From c57638c774578183acffb1ce90b28d8ac7e798e2 Mon Sep 17 00:00:00 2001 From: Mark Banner Date: Fri, 30 Jan 2015 16:01:42 +0000 Subject: [PATCH 001/101] Bug 1093780 Part 1 - Update OpenTok library to v2.4.0 for Loop. r=dmose --- .../loop/content/shared/css/conversation.css | 10 +- .../loop/content/shared/js/mixins.js | 8 +- .../shared/libs/sdk-content/css/ot.css | 440 +- .../libs/sdk-content/js/dynamic_config.min.js | 12 +- .../loop/content/shared/libs/sdk.js | 37438 ++++++++-------- .../test/functional/test_1_browser_call.py | 2 +- 6 files changed, 19883 insertions(+), 18027 deletions(-) diff --git a/browser/components/loop/content/shared/css/conversation.css b/browser/components/loop/content/shared/css/conversation.css index 661a680b666..15acdda5e6a 100644 --- a/browser/components/loop/content/shared/css/conversation.css +++ b/browser/components/loop/content/shared/css/conversation.css @@ -510,7 +510,7 @@ */ .local-stream.local-stream-audio, .standalone .OT_subscriber .OT_video-poster, -.fx-embedded .OT_video-container .OT_video-poster, +.fx-embedded .OT_subscriber .OT_video-poster, .local-stream-audio .OT_publisher .OT_video-poster { background-image: url("../img/audio-call-avatar.svg"); background-repeat: no-repeat; @@ -537,7 +537,7 @@ * Another less ugly possibility would be to work with Ted Mielczarek to use * the fake camera drivers he has for Linux. */ -.room-conversation .OT_publisher .OT_video-container { +.room-conversation .OT_publisher .OT_widget-container { height: 100% !important; width: 100% !important; top: 0 !important; @@ -545,12 +545,12 @@ background-color: transparent; /* avoid visually obvious letterboxing */ } -.room-conversation .OT_publisher .OT_video-container video { +.room-conversation .OT_publisher .OT_widget-container video { background-color: transparent; /* avoid visually obvious letterboxing */ } -.fx-embedded .room-conversation .room-preview .OT_publisher .OT_video-container, -.fx-embedded .room-conversation .room-preview .OT_publisher .OT_video-container video { +.fx-embedded .room-conversation .room-preview .OT_publisher .OT_widget-container, +.fx-embedded .room-conversation .room-preview .OT_publisher .OT_widget-container video { /* Desktop conversation window room preview local stream actually wants a black background */ background-color: #000; diff --git a/browser/components/loop/content/shared/js/mixins.js b/browser/components/loop/content/shared/js/mixins.js index 78b7fa701ee..93dc79e425d 100644 --- a/browser/components/loop/content/shared/js/mixins.js +++ b/browser/components/loop/content/shared/js/mixins.js @@ -198,15 +198,11 @@ loop.shared.mixins = (function() { // @see https://bugzilla.mozilla.org/show_bug.cgi?id=1020445 return { insertMode: "append", + fitMode: "contain", width: "100%", height: "100%", publishVideo: options.publishVideo, - style: { - audioLevelDisplayMode: "off", - buttonDisplayMode: "off", - nameDisplayMode: "off", - videoDisabledDisplayMode: "off" - } + showControls: false, }; }, diff --git a/browser/components/loop/content/shared/libs/sdk-content/css/ot.css b/browser/components/loop/content/shared/libs/sdk-content/css/ot.css index c4de63157a8..3195b3def2b 100644 --- a/browser/components/loop/content/shared/libs/sdk-content/css/ot.css +++ b/browser/components/loop/content/shared/libs/sdk-content/css/ot.css @@ -11,16 +11,15 @@ /* Root OT object, this is where our CSS reset happens */ .OT_root, .OT_root * { - color: #ffffff; - margin: 0; - padding: 0; - border: 0; - font-size: 100%; - font-family: Arial, Helvetica, sans-serif; - vertical-align: baseline; + color: #ffffff; + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font-family: Arial, Helvetica, sans-serif; + vertical-align: baseline; } - /** * Specific Element Reset */ @@ -31,10 +30,10 @@ .OT_root h4, .OT_root h5, .OT_root h6 { - color: #ffffff; - font-family: Arial, Helvetica, sans-serif; - font-size: 100%; - font-weight: bold; + color: #ffffff; + font-family: Arial, Helvetica, sans-serif; + font-size: 100%; + font-weight: bold; } .OT_root header { @@ -62,11 +61,11 @@ } .OT_root strong { - font-weight: bold; + font-weight: bold; } .OT_root em { - font-style: italic; + font-style: italic; } .OT_root a, @@ -74,46 +73,46 @@ .OT_root a:visited, .OT_root a:hover, .OT_root a:active { - font-family: Arial, Helvetica, sans-serif; + font-family: Arial, Helvetica, sans-serif; } - .OT_root ul, .OT_root ol { - margin: 1em 1em 1em 2em; + margin: 1em 1em 1em 2em; } .OT_root ol { - list-style: decimal outside; + list-style: decimal outside; } .OT_root ul { - list-style: disc outside; + list-style: disc outside; } .OT_root dl { - margin: 4px; + margin: 4px; } - .OT_root dl dt, - .OT_root dl dd { - float: left; - margin: 0; - padding: 0; - } - .OT_root dl dt { - clear: left; - text-align: right; - width: 50px; - } - .OT_root dl dd { - margin-left: 10px; - } +.OT_root dl dt, +.OT_root dl dd { + float: left; + margin: 0; + padding: 0; +} + +.OT_root dl dt { + clear: left; + text-align: right; + width: 50px; +} + +.OT_root dl dd { + margin-left: 10px; +} .OT_root img { - border: 0 none; + border: 0 none; } - /* Modal dialog styles */ /* Modal dialog styles */ @@ -166,7 +165,6 @@ text-align: center; } - .OT_dialog-messages-main { margin-bottom: 36px; line-height: 36px; @@ -302,37 +300,34 @@ /* Publisher and Subscriber styles */ .OT_publisher, .OT_subscriber { - position: relative; - min-width: 48px; - min-height: 48px; + position: relative; + min-width: 48px; + min-height: 48px; } -.OT_publisher video, -.OT_subscriber video, -.OT_publisher object, -.OT_subscriber object { - width: 100%; - height: 100%; -} +.OT_publisher .OT_video-element, +.OT_subscriber .OT_video-element { + display: block; + position: absolute; + width: 100%; -.OT_publisher object, -.OT_subscriber object { + transform-origin: 0 0; } /* Styles that are applied when the video element should be mirrored */ -.OT_publisher.OT_mirrored video{ - -webkit-transform: scale(-1, 1); - -moz-transform:scale(-1,1); +.OT_publisher.OT_mirrored .OT_video-element { + transform: scale(-1, 1); + transform-origin: 50% 50%; } .OT_subscriber_error { - background-color: #000; - color: #fff; - text-align: center; + background-color: #000; + color: #fff; + text-align: center; } .OT_subscriber_error > p { - padding: 20px; + padding: 20px; } /* The publisher/subscriber name/mute background */ @@ -346,67 +341,67 @@ .OT_subscriber .OT_archiving-status, .OT_publihser .OT_archiving-light-box, .OT_subscriber .OT_archiving-light-box { - -moz-box-sizing: border-box; - -webkit-box-sizing: border-box; - -ms-box-sizing: border-box; - box-sizing: border-box; - top: 0; - left: 0; - right: 0; - display: block; - height: 34px; - position: absolute; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; + top: 0; + left: 0; + right: 0; + display: block; + height: 34px; + position: absolute; } .OT_publisher .OT_bar, .OT_subscriber .OT_bar { - background: rgba(0, 0, 0, 0.4); + background: rgba(0, 0, 0, 0.4); } .OT_publisher .OT_edge-bar-item, .OT_subscriber .OT_edge-bar-item { - z-index: 1; /* required to get audio level meter underneath */ + z-index: 1; /* required to get audio level meter underneath */ } /* The publisher/subscriber name panel/archiving status bar */ .OT_publisher .OT_name, .OT_subscriber .OT_name { - background-color: transparent; - color: #ffffff; - font-size: 15px; - line-height: 34px; - font-weight: normal; - padding: 0 4px 0 36px; + background-color: transparent; + color: #ffffff; + font-size: 15px; + line-height: 34px; + font-weight: normal; + padding: 0 4px 0 36px; } .OT_publisher .OT_archiving-status, .OT_subscriber .OT_archiving-status { - background: rgba(0, 0, 0, 0.4); - top: auto; - bottom: 0; - left: 34px; - padding: 0 4px; - color: rgba(255,255,255,0.8); - font-size: 15px; - line-height: 34px; - font-weight: normal; + background: rgba(0, 0, 0, 0.4); + top: auto; + bottom: 0; + left: 34px; + padding: 0 4px; + color: rgba(255, 255, 255, 0.8); + font-size: 15px; + line-height: 34px; + font-weight: normal; } .OT_micro .OT_archiving-status, .OT_micro:hover .OT_archiving-status, .OT_mini .OT_archiving-status, .OT_mini:hover .OT_archiving-status { - display: none; + display: none; } .OT_publisher .OT_archiving-light-box, .OT_subscriber .OT_archiving-light-box { - background: rgba(0, 0, 0, 0.4); - top: auto; - bottom: 0; - right: auto; - width: 34px; - height: 34px; + background: rgba(0, 0, 0, 0.4); + top: auto; + bottom: 0; + right: auto; + width: 34px; + height: 34px; } .OT_archiving-light { @@ -423,6 +418,7 @@ -moz-box-shadow: 0 0 5px 1px #575757; box-shadow: 0 0 5px 1px #575757; } + .OT_archiving-light.OT_active { background-color: #970d13; -webkit-animation: OT_pulse 1.3s ease-in; @@ -432,6 +428,7 @@ -moz-animation-iteration-count: infinite; -webkit-animation-iteration-count: infinite; } + @-moz-keyframes OT_pulse { 0% { -webkit-box-shadow: 0 0 0px 0px #c70019; @@ -463,6 +460,7 @@ box-shadow: 0 0 0px 0px #c70019; } } + @-webkit-keyframes OT_pulse { 0% { -webkit-box-shadow: 0 0 0px 0px #c70019; @@ -494,6 +492,7 @@ box-shadow: 0 0 0px 0px #c70019; } } + @-o-keyframes OT_pulse { 0% { -webkit-box-shadow: 0 0 0px 0px #c70019; @@ -525,6 +524,7 @@ box-shadow: 0 0 0px 0px #c70019; } } + @-ms-keyframes OT_pulse { 0% { -webkit-box-shadow: 0 0 0px 0px #c70019; @@ -556,6 +556,7 @@ box-shadow: 0 0 0px 0px #c70019; } } + @-webkit-keyframes OT_pulse { 0% { -webkit-box-shadow: 0 0 0px 0px #c70019; @@ -591,15 +592,15 @@ .OT_mini .OT_bar, .OT_bar.OT_mode-mini, .OT_bar.OT_mode-mini-auto { - bottom: 0; - height: auto; + bottom: 0; + height: auto; } .OT_mini .OT_name.OT_mode-off, .OT_mini .OT_name.OT_mode-on, .OT_mini .OT_name.OT_mode-auto, .OT_mini:hover .OT_name.OT_mode-auto { - display: none; + display: none; } .OT_publisher .OT_name, @@ -624,42 +625,42 @@ .OT_publisher .OT_mute, .OT_subscriber .OT_mute { - right: 0; - top: 0; - border-left: 1px solid rgba(255, 255, 255, 0.2); - height: 36px; - width: 37px; + right: 0; + top: 0; + border-left: 1px solid rgba(255, 255, 255, 0.2); + height: 36px; + width: 37px; } .OT_mini .OT_mute, -.OT_mute.OT_mode-mini, -.OT_mute.OT_mode-mini-auto { - top: 50%; - left: 50%; - right: auto; - margin-top: -18px; - margin-left: -18.5px; - border-left: none; +.OT_publisher.OT_mini .OT_mute.OT_mode-auto.OT_mode-on-hold, +.OT_subscriber.OT_mini .OT_mute.OT_mode-auto.OT_mode-on-hold { + top: 50%; + left: 50%; + right: auto; + margin-top: -18px; + margin-left: -18.5px; + border-left: none; } .OT_publisher .OT_mute { - background-image: url(../images/rtc/mic-on.png); - background-position: 9px 5px; + background-image: url(../images/rtc/mic-on.png); + background-position: 9px 5px; } .OT_publisher .OT_mute.OT_active { - background-image: url(../images/rtc/mic-off.png); - background-position: 9px 4px; + background-image: url(../images/rtc/mic-off.png); + background-position: 9px 4px; } .OT_subscriber .OT_mute { - background-image: url(../images/rtc/speaker-on.png); - background-position: 8px 7px; + background-image: url(../images/rtc/speaker-on.png); + background-position: 8px 7px; } .OT_subscriber .OT_mute.OT_active { - background-image: url(../images/rtc/speaker-off.png); - background-position: 7px 7px; + background-image: url(../images/rtc/speaker-off.png); + background-position: 7px 7px; } /** @@ -672,17 +673,9 @@ /* Default display mode transitions for various chrome elements */ .OT_publisher .OT_edge-bar-item, .OT_subscriber .OT_edge-bar-item { - -ms-transition-property: top, bottom, opacity; - -ms-transition-duration: 0.5s; - -moz-transition-property: top, bottom, opacity; - -moz-transition-duration: 0.5s; - -webkit-transition-property: top, bottom, opacity; - -webkit-transition-duration: 0.5s; - -o-transition-property: top, bottom, opacity; - -o-transition-duration: 0.5s; - transition-property: top, bottom, opacity; - transition-duration: 0.5s; - transition-timing-function: ease-in; + transition-property: top, bottom, opacity; + transition-duration: 0.5s; + transition-timing-function: ease-in; } .OT_publisher .OT_edge-bar-item.OT_mode-off, @@ -691,14 +684,14 @@ .OT_subscriber .OT_edge-bar-item.OT_mode-auto, .OT_publisher .OT_edge-bar-item.OT_mode-mini-auto, .OT_subscriber .OT_edge-bar-item.OT_mode-mini-auto { - top: -25px; - opacity: 0; + top: -25px; + opacity: 0; } .OT_mini .OT_mute.OT_mode-auto, .OT_publisher .OT_mute.OT_mode-mini-auto, .OT_subscriber .OT_mute.OT_mode-mini-auto { - top: 50%; + top: 50%; } .OT_publisher .OT_edge-bar-item.OT_edge-bottom.OT_mode-off, @@ -707,8 +700,8 @@ .OT_subscriber .OT_edge-bar-item.OT_edge-bottom.OT_mode-auto, .OT_publisher .OT_edge-bar-item.OT_edge-bottom.OT_mode-mini-auto, .OT_subscriber .OT_edge-bar-item.OT_edge-bottom.OT_mode-mini-auto { - top: auto; - bottom: -25px; + top: auto; + bottom: -25px; } .OT_publisher .OT_edge-bar-item.OT_mode-on, @@ -719,127 +712,148 @@ .OT_subscriber:hover .OT_edge-bar-item.OT_mode-auto, .OT_publisher:hover .OT_edge-bar-item.OT_mode-mini-auto, .OT_subscriber:hover .OT_edge-bar-item.OT_mode-mini-auto { - top: 0; - opacity: 1; + top: 0; + opacity: 1; } .OT_mini .OT_mute.OT_mode-on, .OT_mini:hover .OT_mute.OT_mode-auto, .OT_mute.OT_mode-mini, -.OT_root:hover .OT_mute.OT_mode-mini-auto { - top: 50%; +.OT_root:hover .OT_mute.OT_mode-mini-auto { + top: 50%; } .OT_publisher .OT_edge-bar-item.OT_edge-bottom.OT_mode-on, .OT_subscriber .OT_edge-bar-item.OT_edge-bottom.OT_mode-on, .OT_publisher:hover .OT_edge-bar-item.OT_edge-bottom.OT_mode-auto, .OT_subscriber:hover .OT_edge-bar-item.OT_edge-bottom.OT_mode-auto { - top: auto; - bottom: 0; - opacity: 1; + top: auto; + bottom: 0; + opacity: 1; } + /* Contains the video element, used to fix video letter-boxing */ -.OT_video-container { - position: absolute; - background-color: #000000; - overflow: hidden; +.OT_widget-container { + position: absolute; + background-color: #000000; + overflow: hidden; } .OT_hidden-audio { - position: absolute !important; - height: 1px !important; - width: 1px !important; + position: absolute !important; + height: 1px !important; + width: 1px !important; } /* Load animation */ .OT_root .OT_video-loading { - background: url('../images/rtc/loader.gif') no-repeat; - display:none; - position: absolute; - width: 32px; - height: 32px; - left: 50%; - top: 50%; - margin-left: -16px; - margin-top: -16px; + background: url('../images/rtc/loader.gif') no-repeat; + display: none; + position: absolute; + width: 32px; + height: 32px; + left: 50%; + top: 50%; + margin-left: -16px; + margin-top: -16px; } .OT_publisher.OT_loading .OT_video-loading, .OT_subscriber.OT_loading .OT_video-loading { - display: block; + display: block; } -.OT_publisher.OT_loading video, -.OT_subscriber.OT_loading video, -.OT_publisher.OT_loading object, -.OT_subscriber.OT_loading object { - display: none; +.OT_publisher.OT_loading .OT_video-element, +.OT_subscriber.OT_loading .OT_video-element { + /*display: none;*/ } +.OT_video-centering { + display: table; + width: 100%; + height: 100%; +} + +.OT_video-container { + display: table-cell; + vertical-align: middle; +} .OT_video-poster { - width: 100%; - height: 100%; - display: none; + position: absolute; + z-index: 1; + width: 100%; + height: 100%; + display: none; - opacity: .25; - background-size: auto 76%; - background-repeat: no-repeat; - background-position: center bottom; - background-image: url(../images/rtc/audioonly-silhouette.svg); + opacity: .25; + + background-repeat: no-repeat; + background-image: url(../images/rtc/audioonly-silhouette.svg); +} + + +.OT_fit-mode-cover .OT_video-poster { + background-size: auto 76%; + background-position: center bottom; +} + +.OT_fit-mode-contain .OT_video-poster { + background-size: contain; + background-position: center; } .OT_audio-level-meter { - position: absolute; - width: 25%; - max-width: 224px; - min-width: 21px; - top: 0; - right: 0; - overflow: hidden; + position: absolute; + width: 25%; + max-width: 224px; + min-width: 21px; + top: 0; + right: 0; + overflow: hidden; } .OT_audio-level-meter:before { - /* makes the height of the container equals its width */ - content: ''; - display: block; - padding-top: 100%; + /* makes the height of the container equals its width */ + content: ''; + display: block; + padding-top: 100%; } .OT_audio-level-meter__bar { - position: absolute; - width: 192%; /* meter value can overflow of 8% */ - height: 192%; - top: -96% /* half of the size */; - right: -96%; - border-radius: 50%; + position: absolute; + width: 192%; /* meter value can overflow of 8% */ + height: 192%; + top: -96% /* half of the size */; + right: -96%; + border-radius: 50%; - background-color: rgba(0, 0, 0, .8); + background-color: rgba(0, 0, 0, .8); } .OT_audio-level-meter__audio-only-img { - position: absolute; - top: 22%; - right: 15%; - width: 40%; + position: absolute; + top: 22%; + right: 15%; + width: 40%; - opacity: .7; + opacity: .7; - background: url(../images/rtc/audioonly-headset.svg) no-repeat center; + background: url(../images/rtc/audioonly-headset.svg) no-repeat center; } .OT_audio-level-meter__audio-only-img:before { - /* makes the height of the container equals its width */ - content: ''; - display: block; - padding-top: 100%; + /* makes the height of the container equals its width */ + content: ''; + display: block; + padding-top: 100%; } .OT_audio-level-meter__value { - position: absolute; - border-radius: 50%; - background-image: radial-gradient(circle, rgba(151,206,0,1) 0%, rgba(151,206,0,0) 100%); + position: absolute; + border-radius: 50%; + background-image: radial-gradient(circle, rgba(151, 206, 0, 1) 0%, rgba(151, 206, 0, 0) 100%); } .OT_audio-level-meter.OT_mode-off { @@ -848,31 +862,31 @@ .OT_audio-level-meter.OT_mode-on, .OT_audio-only .OT_audio-level-meter.OT_mode-auto { - display: block; + display: block; } .OT_video-disabled-indicator { - opacity: 1; - border: none; - display: none; - position: absolute; - background-color: transparent; - background-repeat: no-repeat; - background-position:bottom right; - top: 0; - left: 0; - bottom: 3px; - right: 3px; + opacity: 1; + border: none; + display: none; + position: absolute; + background-color: transparent; + background-repeat: no-repeat; + background-position: bottom right; + top: 0; + left: 0; + bottom: 3px; + right: 3px; } .OT_video-disabled { - background-image: url(../images/rtc/video-disabled.png); + background-image: url(../images/rtc/video-disabled.png); } .OT_video-disabled-warning { - background-image: url(../images/rtc/video-disabled-warning.png); + background-image: url(../images/rtc/video-disabled-warning.png); } .OT_video-disabled-indicator.OT_active { - display: block; + display: block; } diff --git a/browser/components/loop/content/shared/libs/sdk-content/js/dynamic_config.min.js b/browser/components/loop/content/shared/libs/sdk-content/js/dynamic_config.min.js index e03661d4bec..fffdb8736cd 100644 --- a/browser/components/loop/content/shared/libs/sdk-content/js/dynamic_config.min.js +++ b/browser/components/loop/content/shared/libs/sdk-content/js/dynamic_config.min.js @@ -1,7 +1,7 @@ -/* - - Copyright (c) 2014 TokBox, Inc. - Released under the MIT license - http://opensource.org/licenses/MIT -*/ +/** + * @license + * Copyright (c) 2014 TokBox, Inc. + * Released under the MIT license + * http://opensource.org/licenses/MIT + */ !function(){TB.Config.replaceWith({global:{exceptionLogging:{enabled:!0,messageLimitPerPartner:100},iceServers:{enabled:!1},instrumentation:{enabled:!1,debugging:!1},tokshow:{textchat:!0}},partners:{change878:{instrumentation:{enabled:!0,debugging:!0}}}})}(TB); \ No newline at end of file diff --git a/browser/components/loop/content/shared/libs/sdk.js b/browser/components/loop/content/shared/libs/sdk.js index a3a701744cb..531ed773f1d 100755 --- a/browser/components/loop/content/shared/libs/sdk.js +++ b/browser/components/loop/content/shared/libs/sdk.js @@ -1,66 +1,30 @@ /** - * @license OpenTok JavaScript Library v2.2.9.7 + * @license OpenTok JavaScript Library v2.4.0 54ae164 HEAD * http://www.tokbox.com/ * * Copyright (c) 2014 TokBox, Inc. * Released under the MIT license * http://opensource.org/licenses/MIT * - * Date: January 26 03:18:02 2015 + * Date: January 08 08:54:40 2015 */ -(function(window) { - if (!window.OT) window.OT = {}; - OT.properties = { - version: 'v2.2.9.7', // The current version (eg. v2.0.4) (This is replaced by gradle) - build: '59e99bc', // The current build hash (This is replaced by gradle) +!(function(window) { - // Whether or not to turn on debug logging by default - debug: 'false', - // The URL of the tokbox website - websiteURL: 'http://www.tokbox.com', +!(function(window, OTHelpers, undefined) { - // The URL of the CDN - cdnURL: 'http://static.opentok.com', - // The URL to use for logging - loggingURL: 'http://hlg.tokbox.com/prod', - // The anvil API URL - apiURL: 'http://anvil.opentok.com', - - // What protocol to use when connecting to the rumor web socket - messagingProtocol: 'wss', - // What port to use when connection to the rumor web socket - messagingPort: 443, - - // If this environment supports SSL - supportSSL: 'true', - // The CDN to use if we're using SSL - cdnURLSSL: 'https://static.opentok.com', - // The URL to use for logging - loggingURLSSL: 'https://hlg.tokbox.com/prod', - // The anvil API URL to use if we're using SSL - apiURLSSL: 'https://anvil.opentok.com', - - minimumVersion: { - firefox: parseFloat('29'), - chrome: parseFloat('34') - } - }; - -})(window); /** - * @license Common JS Helpers on OpenTok 0.2.0 3fa583f master + * @license Common JS Helpers on OpenTok 0.2.0 ef06638 2014Q4-2.2 * http://www.tokbox.com/ * - * Copyright (c) 2014 TokBox, Inc. - * Released under the MIT license - * http://opensource.org/licenses/MIT + * Copyright (c) 2015 TokBox, Inc. * - * Date: August 08 12:31:42 2014 + * Date: January 08 08:54:29 2015 * */ + // OT Helper Methods // // helpers.js <- the root file @@ -68,182 +32,359 @@ // (i.e. video, dom, etc) // // @example Getting a DOM element by it's id -// var element = OTHelpers('domId'); +// var element = OTHelpers('#domId'); // // /*jshint browser:true, smarttabs:true*/ -!(function(window, undefined) { +// Short cuts to commonly accessed prototypes +var prototypeSlice = Array.prototype.slice; - var OTHelpers = function(domId) { - return document.getElementById(domId); +// RegEx to detect CSS Id selectors +var detectIdSelectors = /^#([\w-]*)$/; + +// The top-level namespace, also performs basic DOMElement selecting. +// +// @example Get the DOM element with the id of 'domId' +// OTHelpers('#domId') +// +// @example Get all video elements +// OTHelpers('video') +// +// @example Get all elements with the class name of 'foo' +// OTHelpers('.foo') +// +// @example Get all elements with the class name of 'foo', +// and do something with the first. +// var collection = OTHelpers('.foo'); +// console.log(collection.first); +// +// +// The second argument is the context, that is document or parent Element, to +// select from. +// +// @example Get a video element within the element with the id of 'domId' +// OTHelpers('video', OTHelpers('#domId')) +// +// +// +// OTHelpers will accept any of the following and return a collection: +// OTHelpers() +// OTHelpers('css selector', optionalParentNode) +// OTHelpers(DomNode) +// OTHelpers([array of DomNode]) +// +// The collection is a ElementCollection object, see the ElementCollection docs for usage info. +// +var OTHelpers = function(selector, context) { + var results = []; + + if (typeof(selector) === 'string') { + var idSelector = detectIdSelectors.exec(selector); + context = context || document; + + if (idSelector && idSelector[1]) { + var element = context.getElementById(idSelector[1]); + if (element) results.push(element); + } + else { + results = context.querySelectorAll(selector); + } + } + else if (selector.nodeType || window.XMLHttpRequest && selector instanceof XMLHttpRequest) { + // allow OTHelpers(DOMNode) and OTHelpers(xmlHttpRequest) + results = [selector]; + context = selector; + } + else if (OTHelpers.isArray(selector)) { + results = selector.slice(); + context = null; + } + + return new ElementCollection(results, context); +}; + +// alias $ to OTHelpers for internal use. This is not exposed outside of OTHelpers. +var $ = OTHelpers; + +// A helper for converting a NodeList to a JS Array +var nodeListToArray = function nodeListToArray (nodes) { + if ($.env.name !== 'IE' || $.env.version > 9) { + return prototypeSlice.call(nodes); + } + + // IE 9 and below call use Array.prototype.slice.call against + // a NodeList, hence the following + var array = []; + + for (var i=0, num=nodes.length; i= length) { - return -1; - } + if (!this) { + throw new TypeError(); + } - if (pivot < 0) { - pivot = length - Math.abs(pivot); - } + length = this.length; - for (i = pivot; i < length; i++) { - if (this[i] === searchElement) { - return i; - } - } + if (length === 0 || pivot >= length) { return -1; - }; + } - OTHelpers.arrayIndexOf = function(array, searchElement, fromIndex) { - return _indexOf.call(array, searchElement, fromIndex); - }; + if (pivot < 0) { + pivot = length - Math.abs(pivot); + } - var _bind = Function.prototype.bind || function() { - var args = Array.prototype.slice.call(arguments), - ctx = args.shift(), - fn = this; - return function() { - return fn.apply(ctx, args.concat(Array.prototype.slice.call(arguments))); - }; - }; + for (i = pivot; i < length; i++) { + if (this[i] === searchElement) { + return i; + } + } + return -1; +}; - OTHelpers.bind = function() { - var args = Array.prototype.slice.call(arguments), - fn = args.shift(); - return _bind.apply(fn, args); - }; +OTHelpers.arrayIndexOf = function(array, searchElement, fromIndex) { + return _indexOf.call(array, searchElement, fromIndex); +}; - var _trim = String.prototype.trim || function() { - return this.replace(/^\s+|\s+$/g, ''); +var _bind = Function.prototype.bind || function() { + var args = prototypeSlice.call(arguments), + ctx = args.shift(), + fn = this; + return function() { + return fn.apply(ctx, args.concat(prototypeSlice.call(arguments))); }; +}; - OTHelpers.trim = function(str) { - return _trim.call(str); - }; +OTHelpers.bind = function() { + var args = prototypeSlice.call(arguments), + fn = args.shift(); + return _bind.apply(fn, args); +}; +var _trim = String.prototype.trim || function() { + return this.replace(/^\s+|\s+$/g, ''); +}; + +OTHelpers.trim = function(str) { + return _trim.call(str); +}; + +OTHelpers.noConflict = function() { OTHelpers.noConflict = function() { - OTHelpers.noConflict = function() { - return OTHelpers; - }; - window.OTHelpers = previousOTHelpers; return OTHelpers; }; + window.OTHelpers = previousOTHelpers; + return OTHelpers; +}; - OTHelpers.isNone = function(obj) { - return obj === undefined || obj === null; +OTHelpers.isNone = function(obj) { + return obj === void 0 || obj === null; +}; + +OTHelpers.isObject = function(obj) { + return obj === Object(obj); +}; + +OTHelpers.isFunction = function(obj) { + return !!obj && (obj.toString().indexOf('()') !== -1 || + Object.prototype.toString.call(obj) === '[object Function]'); +}; + +OTHelpers.isArray = OTHelpers.isFunction(Array.isArray) && Array.isArray || + function (vArg) { + return Object.prototype.toString.call(vArg) === '[object Array]'; }; - OTHelpers.isObject = function(obj) { - return obj === Object(obj); - }; +OTHelpers.isEmpty = function(obj) { + if (obj === null || obj === void 0) return true; + if (OTHelpers.isArray(obj) || typeof(obj) === 'string') return obj.length === 0; - OTHelpers.isFunction = function(obj) { - return !!obj && (obj.toString().indexOf('()') !== -1 || - Object.prototype.toString.call(obj) === '[object Function]'); - }; + // Objects without enumerable owned properties are empty. + for (var key in obj) { + if (obj.hasOwnProperty(key)) return false; + } - OTHelpers.isArray = OTHelpers.isFunction(Array.isArray) && Array.isArray || - function (vArg) { - return Object.prototype.toString.call(vArg) === '[object Array]'; - }; - - OTHelpers.isEmpty = function(obj) { - if (obj === null || obj === undefined) return true; - if (OTHelpers.isArray(obj) || typeof(obj) === 'string') return obj.length === 0; - - // Objects without enumerable owned properties are empty. - for (var key in obj) { - if (obj.hasOwnProperty(key)) return false; - } - - return true; - }; + return true; +}; // Extend a target object with the properties from one or // more source objects @@ -251,18 +392,18 @@ // @example: // dest = OTHelpers.extend(dest, source1, source2, source3); // - OTHelpers.extend = function(/* dest, source1[, source2, ..., , sourceN]*/) { - var sources = Array.prototype.slice.call(arguments), - dest = sources.shift(); +OTHelpers.extend = function(/* dest, source1[, source2, ..., , sourceN]*/) { + var sources = prototypeSlice.call(arguments), + dest = sources.shift(); - OTHelpers.forEach(sources, function(source) { - for (var key in source) { - dest[key] = source[key]; - } - }); + OTHelpers.forEach(sources, function(source) { + for (var key in source) { + dest[key] = source[key]; + } + }); - return dest; - }; + return dest; +}; // Ensures that the target object contains certain defaults. // @@ -271,28 +412,28 @@ // loading: true // loading by default // }); // - OTHelpers.defaults = function(/* dest, defaults1[, defaults2, ..., , defaultsN]*/) { - var sources = Array.prototype.slice.call(arguments), - dest = sources.shift(); +OTHelpers.defaults = function(/* dest, defaults1[, defaults2, ..., , defaultsN]*/) { + var sources = prototypeSlice.call(arguments), + dest = sources.shift(); - OTHelpers.forEach(sources, function(source) { - for (var key in source) { - if (dest[key] === void 0) dest[key] = source[key]; - } - }); + OTHelpers.forEach(sources, function(source) { + for (var key in source) { + if (dest[key] === void 0) dest[key] = source[key]; + } + }); - return dest; - }; + return dest; +}; - OTHelpers.clone = function(obj) { - if (!OTHelpers.isObject(obj)) return obj; - return OTHelpers.isArray(obj) ? obj.slice() : OTHelpers.extend({}, obj); - }; +OTHelpers.clone = function(obj) { + if (!OTHelpers.isObject(obj)) return obj; + return OTHelpers.isArray(obj) ? obj.slice() : OTHelpers.extend({}, obj); +}; // Handy do nothing function - OTHelpers.noop = function() {}; +OTHelpers.noop = function() {}; // Returns the number of millisceonds since the the UNIX epoch, this is functionally @@ -300,102 +441,32 @@ // // Where available, we use 'performance.now' which is more accurate and reliable, // otherwise we default to new Date().getTime(). - OTHelpers.now = (function() { - var performance = window.performance || {}, - navigationStart, - now = performance.now || - performance.mozNow || - performance.msNow || - performance.oNow || - performance.webkitNow; +OTHelpers.now = (function() { + var performance = window.performance || {}, + navigationStart, + now = performance.now || + performance.mozNow || + performance.msNow || + performance.oNow || + performance.webkitNow; - if (now) { - now = OTHelpers.bind(now, performance); - navigationStart = performance.timing.navigationStart; + if (now) { + now = OTHelpers.bind(now, performance); + navigationStart = performance.timing.navigationStart; - return function() { return navigationStart + now(); }; - } else { - return function() { return new Date().getTime(); }; - } - })(); - - var _browser = function() { - var userAgent = window.navigator.userAgent.toLowerCase(), - appName = window.navigator.appName, - navigatorVendor, - browser = 'unknown', - version = -1; - - if (userAgent.indexOf('opera') > -1 || userAgent.indexOf('opr') > -1) { - browser = 'Opera'; - - if (/opr\/([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) { - version = parseFloat( RegExp.$1 ); - } - - } else if (userAgent.indexOf('firefox') > -1) { - browser = 'Firefox'; - - if (/firefox\/([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) { - version = parseFloat( RegExp.$1 ); - } - - } else if (appName === 'Microsoft Internet Explorer') { - // IE 10 and below - browser = 'IE'; - - if (/msie ([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) { - version = parseFloat( RegExp.$1 ); - } - - } else if (appName === 'Netscape' && userAgent.indexOf('trident') > -1) { - // IE 11+ - - browser = 'IE'; - - if (/trident\/.*rv:([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) { - version = parseFloat( RegExp.$1 ); - } - - } else if (userAgent.indexOf('chrome') > -1) { - browser = 'Chrome'; - - if (/chrome\/([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) { - version = parseFloat( RegExp.$1 ); - } - - } else if ((navigatorVendor = window.navigator.vendor) && - navigatorVendor.toLowerCase().indexOf('apple') > -1) { - browser = 'Safari'; - - if (/version\/([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) { - version = parseFloat( RegExp.$1 ); - } - } - - return { - browser: browser, - version: version, - iframeNeedsLoad: userAgent.indexOf('webkit') < 0 - }; - }(); - - OTHelpers.browser = function() { - return _browser.browser; - }; - - OTHelpers.browserVersion = function() { - return _browser; - }; - - - OTHelpers.canDefineProperty = true; - - try { - Object.defineProperty({}, 'x', {}); - } catch (err) { - OTHelpers.canDefineProperty = false; + return function() { return navigationStart + now(); }; + } else { + return function() { return new Date().getTime(); }; } +})(); + +OTHelpers.canDefineProperty = true; + +try { + Object.defineProperty({}, 'x', {}); +} catch (err) { + OTHelpers.canDefineProperty = false; +} // A helper for defining a number of getters at once. // @@ -409,105 +480,64 @@ // id: function() { return _sessionId; } // }); // - OTHelpers.defineGetters = function(self, getters, enumerable) { - var propsDefinition = {}; +OTHelpers.defineGetters = function(self, getters, enumerable) { + var propsDefinition = {}; - if (enumerable === void 0) enumerable = false; + if (enumerable === void 0) enumerable = false; - for (var key in getters) { - propsDefinition[key] = { - get: getters[key], - enumerable: enumerable - }; - } + for (var key in getters) { + propsDefinition[key] = { + get: getters[key], + enumerable: enumerable + }; + } - OTHelpers.defineProperties(self, propsDefinition); - }; + OTHelpers.defineProperties(self, propsDefinition); +}; - var generatePropertyFunction = function(object, getter, setter) { - if(getter && !setter) { - return function() { - return getter.call(object); - }; - } else if(getter && setter) { - return function(value) { - if(value !== void 0) { - setter.call(object, value); - } - return getter.call(object); - }; - } else { - return function(value) { - if(value !== void 0) { - setter.call(object, value); - } - }; - } - }; +var generatePropertyFunction = function(object, getter, setter) { + if(getter && !setter) { + return function() { + return getter.call(object); + }; + } else if(getter && setter) { + return function(value) { + if(value !== void 0) { + setter.call(object, value); + } + return getter.call(object); + }; + } else { + return function(value) { + if(value !== void 0) { + setter.call(object, value); + } + }; + } +}; - OTHelpers.defineProperties = function(object, getterSetters) { - for (var key in getterSetters) { - object[key] = generatePropertyFunction(object, getterSetters[key].get, - getterSetters[key].set); - } - }; +OTHelpers.defineProperties = function(object, getterSetters) { + for (var key in getterSetters) { + object[key] = generatePropertyFunction(object, getterSetters[key].get, + getterSetters[key].set); + } +}; // Polyfill Object.create for IE8 // // See https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/create // - if (!Object.create) { - Object.create = function (o) { - if (arguments.length > 1) { - throw new Error('Object.create implementation only accepts the first parameter.'); - } - function F() {} - F.prototype = o; - return new F(); - }; - } - - OTHelpers.setCookie = function(key, value) { - try { - localStorage.setItem(key, value); - } catch (err) { - // Store in browser cookie - var date = new Date(); - date.setTime(date.getTime()+(365*24*60*60*1000)); - var expires = '; expires=' + date.toGMTString(); - document.cookie = key + '=' + value + expires + '; path=/'; +if (!Object.create) { + Object.create = function (o) { + if (arguments.length > 1) { + throw new Error('Object.create implementation only accepts the first parameter.'); } + function F() {} + F.prototype = o; + return new F(); }; - - OTHelpers.getCookie = function(key) { - var value; - - try { - value = localStorage.getItem('opentok_client_id'); - return value; - } catch (err) { - // Check browser cookies - var nameEQ = key + '='; - var ca = document.cookie.split(';'); - for(var i=0;i < ca.length;i++) { - var c = ca[i]; - while (c.charAt(0) === ' ') { - c = c.substring(1,c.length); - } - if (c.indexOf(nameEQ) === 0) { - value = c.substring(nameEQ.length,c.length); - } - } - - if (value) { - return value; - } - } - - return null; - }; - +} // These next bits are included from Underscore.js. The original copyright // notice is below. @@ -517,191 +547,65 @@ // (c) 2011-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors // Underscore may be freely distributed under the MIT license. - // Invert the keys and values of an object. The values must be serializable. - OTHelpers.invert = function(obj) { - var result = {}; - for (var key in obj) if (obj.hasOwnProperty(key)) result[obj[key]] = key; - return result; - }; - - - // List of HTML entities for escaping. - var entityMap = { - escape: { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - '\'': ''', - '/': '/' - } - }; - - entityMap.unescape = OTHelpers.invert(entityMap.escape); - - // Regexes containing the keys and values listed immediately above. - var entityRegexes = { - escape: new RegExp('[' + OTHelpers.keys(entityMap.escape).join('') + ']', 'g'), - unescape: new RegExp('(' + OTHelpers.keys(entityMap.unescape).join('|') + ')', 'g') - }; - - // Functions for escaping and unescaping strings to/from HTML interpolation. - OTHelpers.forEach(['escape', 'unescape'], function(method) { - OTHelpers[method] = function(string) { - if (string === null || string === undefined) return ''; - return ('' + string).replace(entityRegexes[method], function(match) { - return entityMap[method][match]; - }); - }; - }); - -// By default, Underscore uses ERB-style template delimiters, change the -// following template settings to use alternative delimiters. - OTHelpers.templateSettings = { - evaluate : /<%([\s\S]+?)%>/g, - interpolate : /<%=([\s\S]+?)%>/g, - escape : /<%-([\s\S]+?)%>/g - }; - -// When customizing `templateSettings`, if you don't want to define an -// interpolation, evaluation or escaping regex, we need one that is -// guaranteed not to match. - var noMatch = /(.)^/; - -// Certain characters need to be escaped so that they can be put into a -// string literal. - var escapes = { - '\'': '\'', - '\\': '\\', - '\r': 'r', - '\n': 'n', - '\t': 't', - '\u2028': 'u2028', - '\u2029': 'u2029' - }; - - var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; - -// JavaScript micro-templating, similar to John Resig's implementation. -// Underscore templating handles arbitrary delimiters, preserves whitespace, -// and correctly escapes quotes within interpolated code. - OTHelpers.template = function(text, data, settings) { - var render; - settings = OTHelpers.defaults({}, settings, OTHelpers.templateSettings); - - // Combine delimiters into one regular expression via alternation. - var matcher = new RegExp([ - (settings.escape || noMatch).source, - (settings.interpolate || noMatch).source, - (settings.evaluate || noMatch).source - ].join('|') + '|$', 'g'); - - // Compile the template source, escaping string literals appropriately. - var index = 0; - var source = '__p+=\''; - text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { - source += text.slice(index, offset) - .replace(escaper, function(match) { return '\\' + escapes[match]; }); - - if (escape) { - source += '\'+\n((__t=(' + escape + '))==null?\'\':OTHelpers.escape(__t))+\n\''; - } - if (interpolate) { - source += '\'+\n((__t=(' + interpolate + '))==null?\'\':__t)+\n\''; - } - if (evaluate) { - source += '\';\n' + evaluate + '\n__p+=\''; - } - index = offset + match.length; - return match; - }); - source += '\';\n'; - - // If a variable is not specified, place data values in local scope. - if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; - - source = 'var __t,__p=\'\',__j=Array.prototype.join,' + - 'print=function(){__p+=__j.call(arguments,\'\');};\n' + - source + 'return __p;\n'; - - try { - // evil is necessary for the new Function line - /*jshint evil:true */ - render = new Function(settings.variable || 'obj', source); - } catch (e) { - e.source = source; - throw e; - } - - if (data) return render(data); - var template = function(data) { - return render.call(this, data); - }; - - // Provide the compiled function source as a convenience for precompilation. - template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; - - return template; - }; - -})(window); +// Invert the keys and values of an object. The values must be serializable. +OTHelpers.invert = function(obj) { + var result = {}; + for (var key in obj) if (obj.hasOwnProperty(key)) result[obj[key]] = key; + return result; +}; /*jshint browser:true, smarttabs:true*/ // tb_require('../../helpers.js') -(function(window, OTHelpers, undefined) { +OTHelpers.statable = function(self, possibleStates, initialState, stateChanged, + stateChangedFailed) { + var previousState, + currentState = self.currentState = initialState; - OTHelpers.statable = function(self, possibleStates, initialState, stateChanged, - stateChangedFailed) { - var previousState, - currentState = self.currentState = initialState; - - var setState = function(state) { - if (currentState !== state) { - if (OTHelpers.arrayIndexOf(possibleStates, state) === -1) { - if (stateChangedFailed && OTHelpers.isFunction(stateChangedFailed)) { - stateChangedFailed('invalidState', state); - } - return; + var setState = function(state) { + if (currentState !== state) { + if (OTHelpers.arrayIndexOf(possibleStates, state) === -1) { + if (stateChangedFailed && OTHelpers.isFunction(stateChangedFailed)) { + stateChangedFailed('invalidState', state); } - - self.previousState = previousState = currentState; - self.currentState = currentState = state; - if (stateChanged && OTHelpers.isFunction(stateChanged)) stateChanged(state, previousState); + return; } - }; - - // Returns a number of states and returns true if the current state - // is any of them. - // - // @example - // if (this.is('connecting', 'connected')) { - // // do some stuff - // } - // - self.is = function (/* state0:String, state1:String, ..., stateN:String */) { - return OTHelpers.arrayIndexOf(arguments, currentState) !== -1; - }; - - - // Returns a number of states and returns true if the current state - // is none of them. - // - // @example - // if (this.isNot('connecting', 'connected')) { - // // do some stuff - // } - // - self.isNot = function (/* state0:String, state1:String, ..., stateN:String */) { - return OTHelpers.arrayIndexOf(arguments, currentState) === -1; - }; - - return setState; + self.previousState = previousState = currentState; + self.currentState = currentState = state; + if (stateChanged && OTHelpers.isFunction(stateChanged)) stateChanged(state, previousState); + } }; -})(window, window.OTHelpers); + + // Returns a number of states and returns true if the current state + // is any of them. + // + // @example + // if (this.is('connecting', 'connected')) { + // // do some stuff + // } + // + self.is = function (/* state0:String, state1:String, ..., stateN:String */) { + return OTHelpers.arrayIndexOf(arguments, currentState) !== -1; + }; + + + // Returns a number of states and returns true if the current state + // is none of them. + // + // @example + // if (this.isNot('connecting', 'connected')) { + // // do some stuff + // } + // + self.isNot = function (/* state0:String, state1:String, ..., stateN:String */) { + return OTHelpers.arrayIndexOf(arguments, currentState) === -1; + }; + + return setState; +}; /*! * This is a modified version of Robert Kieffer awesome uuid.js library. @@ -720,7 +624,7 @@ /*global crypto:true, Uint32Array:true, Buffer:true */ /*jshint browser:true, smarttabs:true*/ -(function(window, OTHelpers, undefined) { +(function() { // Unique ID creation requires a high quality random # generator, but // Math.random() does not guarantee "cryptographic quality". So we feature @@ -843,202 +747,795 @@ OTHelpers.uuid = uuid; -}(window, window.OTHelpers)); -/*jshint browser:true, smarttabs:true*/ +}()); +/*jshint browser:true, smarttabs:true */ // tb_require('../helpers.js') -(function(window, OTHelpers, undefined) { - OTHelpers.useLogHelpers = function(on){ +var getErrorLocation; - // Log levels for OTLog.setLogLevel - on.DEBUG = 5; - on.LOG = 4; - on.INFO = 3; - on.WARN = 2; - on.ERROR = 1; - on.NONE = 0; +// Properties that we'll acknowledge from the JS Error object +var safeErrorProps = [ + 'description', + 'fileName', + 'lineNumber', + 'message', + 'name', + 'number', + 'stack' +]; - var _logLevel = on.NONE, - _logs = [], - _canApplyConsole = true; - try { - Function.prototype.bind.call(window.console.log, window.console); - } catch (err) { - _canApplyConsole = false; +// OTHelpers.Error +// +// A construct to contain error information that also helps with extracting error +// context, such as stack trace. +// +// @constructor +// @memberof OTHelpers +// @method Error +// +// @param {String} message +// Optional. The error message +// +// @param {Object} props +// Optional. A dictionary of properties containing extra Error info. +// +// +// @example Create a simple error with juts a custom message +// var error = new OTHelpers.Error('Something Broke!'); +// error.message === 'Something Broke!'; +// +// @example Create an Error with a message and a name +// var error = new OTHelpers.Error('Something Broke!', 'FooError'); +// error.message === 'Something Broke!'; +// error.name === 'FooError'; +// +// @example Create an Error with a message, name, and custom properties +// var error = new OTHelpers.Error('Something Broke!', 'FooError', { +// foo: 'bar', +// listOfImportantThings: [1,2,3,4] +// }); +// error.message === 'Something Broke!'; +// error.name === 'FooError'; +// error.foo === 'bar'; +// error.listOfImportantThings == [1,2,3,4]; +// +// @example Create an Error from a Javascript Error +// var error = new OTHelpers.Error(domSyntaxError); +// error.message === domSyntaxError.message; +// error.name === domSyntaxError.name === 'SyntaxError'; +// // ...continues for each properties of domSyntaxError +// +// @references +// * https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi +// * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/Stack +// * http://www.w3.org/TR/dom/#interface-domerror +// +// +// @todo +// * update usage in OTMedia +// * replace error handling in OT.js +// * normalise stack behaviour under Chrome/Node/Safari with other browsers +// * unit test for stack parsing +// +// +OTHelpers.Error = function (message, name, props) { + switch (arguments.length) { + case 1: + if ($.isObject(message)) { + props = message; + name = void 0; + message = void 0; + } + // Otherwise it's the message + break; + + case 2: + if ($.isObject(name)) { + props = name; + name = void 0; + } + // Otherwise name is actually the name + + break; + } + + if ( props instanceof Error) { + // Special handling of this due to Chrome weirdness. It seems that + // properties of the Error object, and it's children, are not + // enumerable in Chrome? + for (var i = 0, num = safeErrorProps.length; i < num; ++i) { + this[safeErrorProps[i]] = props[safeErrorProps[i]]; + } + } + else if ( $.isObject(props)) { + // Use an custom properties that are provided + for (var key in props) { + if (props.hasOwnProperty(key)) { + this[key] = props[key]; + } + } + } + + // If any of the fundamental properties are missing then try and + // extract them. + if ( !(this.fileName && this.lineNumber && this.columnNumber && this.stack) ) { + var err = getErrorLocation(); + + if (!this.fileName && err.fileName) { + this.fileName = err.fileName; } - // Some objects can't be logged in the console, mostly these are certain - // types of native objects that are exposed to JS. This is only really a - // problem with IE, hence only the IE version does anything. - var makeLogArgumentsSafe = function(args) { return args; }; - - if (OTHelpers.browser() === 'IE') { - makeLogArgumentsSafe = function(args) { - return [toDebugString(Array.prototype.slice.apply(args))]; - }; + if (!this.lineNumber && err.lineNumber) { + this.lineNumber = err.lineNumber; } - // Generates a logging method for a particular method and log level. - // - // Attempts to handle the following cases: - // * the desired log method doesn't exist, call fallback (if available) instead - // * the console functionality isn't available because the developer tools (in IE) - // aren't open, call fallback (if available) - // * attempt to deal with weird IE hosted logging methods as best we can. - // - function generateLoggingMethod(method, level, fallback) { - return function() { - if (on.shouldLog(level)) { - var cons = window.console, - args = makeLogArgumentsSafe(arguments); - - // In IE, window.console may not exist if the developer tools aren't open - // This also means that cons and cons[method] can appear at any moment - // hence why we retest this every time. - if (cons && cons[method]) { - // the desired console method isn't a real object, which means - // that we can't use apply on it. We force it to be a real object - // using Function.bind, assuming that's available. - if (cons[method].apply || _canApplyConsole) { - if (!cons[method].apply) { - cons[method] = Function.prototype.bind.call(cons[method], cons); - } - - cons[method].apply(cons, args); - } - else { - // This isn't the same result as the above, but it's better - // than nothing. - cons[method](args); - } - } - else if (fallback) { - fallback.apply(on, args); - - // Skip appendToLogs, we delegate entirely to the fallback - return; - } - - appendToLogs(method, makeLogArgumentsSafe(arguments)); - } - }; + if (!this.columnNumber && err.columnNumber) { + this.columnNumber = err.columnNumber; } - on.log = generateLoggingMethod('log', on.LOG); + if (!this.stack && err.stack) { + this.stack = err.stack; + } + } - // Generate debug, info, warn, and error logging methods, these all fallback to on.log - on.debug = generateLoggingMethod('debug', on.DEBUG, on.log); - on.info = generateLoggingMethod('info', on.INFO, on.log); - on.warn = generateLoggingMethod('warn', on.WARN, on.log); - on.error = generateLoggingMethod('error', on.ERROR, on.log); + if (!this.message && message) this.message = message; + if (!this.name && name) this.name = name; +}; + +OTHelpers.Error.prototype.toString = +OTHelpers.Error.prototype.valueOf = function() { + var locationDetails = ''; + if (this.fileName) locationDetails += ' ' + this.fileName; + if (this.lineNumber) { + locationDetails += ' ' + this.lineNumber; + if (this.columnNumber) locationDetails += ':' + this.columnNumber; + } + + return '<' + (this.name ? this.name + ' ' : '') + this.message + locationDetails + '>'; +}; - on.setLogLevel = function(level) { - _logLevel = typeof(level) === 'number' ? level : 0; - on.debug('TB.setLogLevel(' + _logLevel + ')'); - return _logLevel; +// Normalise err.stack so that it is the same format as the other browsers +// We skip the first two frames so that we don't capture getErrorLocation() and +// the callee. +// +// Used by Environments that support the StackTrace API. (Chrome, Node, Opera) +// +var prepareStackTrace = function prepareStackTrace (_, stack){ + return $.map(stack.slice(2), function(frame) { + var _f = { + fileName: frame.getFileName(), + linenumber: frame.getLineNumber(), + columnNumber: frame.getColumnNumber() }; - on.getLogs = function() { - return _logs; - }; + if (frame.getFunctionName()) _f.functionName = frame.getFunctionName(); + if (frame.getMethodName()) _f.methodName = frame.getMethodName(); + if (frame.getThis()) _f.self = frame.getThis(); - // Determine if the level is visible given the current logLevel. - on.shouldLog = function(level) { - return _logLevel >= level; - }; + return _f; + }); +}; - // Format the current time nicely for logging. Returns the current - // local time. - function formatDateStamp() { - var now = new Date(); - return now.toLocaleTimeString() + now.getMilliseconds(); + +// Black magic to retrieve error location info for various environments +getErrorLocation = function getErrorLocation () { + var info = {}, + callstack, + errLocation, + err; + + switch ($.env.name) { + case 'Firefox': + case 'Safari': + case 'IE': + + if ($.env.name === 'IE') { + err = new Error(); } - - function toJson(object) { + else { try { - return JSON.stringify(object); - } catch(e) { - return object.toString(); + window.call.js.is.explody; + } + catch(e) { err = e; } + } + + callstack = err.stack.split('\n'); + + //Remove call to getErrorLocation() and the callee + callstack.shift(); + callstack.shift(); + + info.stack = callstack; + + if ($.env.name === 'IE') { + // IE also includes the error message in it's stack trace + info.stack.shift(); + + // each line begins with some amounts of spaces and 'at', we remove + // these to normalise with the other browsers. + info.stack = $.map(callstack, function(call) { + return call.replace(/^\s+at\s+/g, ''); + }); + } + + errLocation = /@(.+?):([0-9]+)(:([0-9]+))?$/.exec(callstack[0]); + if (errLocation) { + info.fileName = errLocation[1]; + info.lineNumber = parseInt(errLocation[2], 10); + if (errLocation.length > 3) info.columnNumber = parseInt(errLocation[4], 10); + } + break; + + case 'Chrome': + case 'Node': + case 'Opera': + var currentPST = Error.prepareStackTrace; + Error.prepareStackTrace = prepareStackTrace; + err = new Error(); + info.stack = err.stack; + Error.prepareStackTrace = currentPST; + + var topFrame = info.stack[0]; + info.lineNumber = topFrame.lineNumber; + info.columnNumber = topFrame.columnNumber; + info.fileName = topFrame.fileName; + if (topFrame.functionName) info.functionName = topFrame.functionName; + if (topFrame.methodName) info.methodName = topFrame.methodName; + if (topFrame.self) info.self = topFrame.self; + break; + + default: + err = new Error(); + if (err.stack) info.stack = err.stack.split('\n'); + break; + } + + if (err.message) info.message = err.message; + return info; +}; + + +/*jshint browser:true, smarttabs:true*/ +/* global process */ + +// tb_require('../helpers.js') + + +// OTHelpers.env +// +// Contains information about the current environment. +// * **OTHelpers.env.name** The name of the Environment (Chrome, FF, Node, etc) +// * **OTHelpers.env.version** Usually a Float, except in Node which uses a String +// * **OTHelpers.env.userAgent** The raw user agent +// * **OTHelpers.env.versionGreaterThan** A helper method that returns true if the +// current version is greater than the argument +// +// Example +// if (OTHelpers.env.versionGreaterThan('0.10.30')) { +// // do something +// } +// +(function() { + // @todo make exposing userAgent unnecessary + var version = -1; + + // Returns true if otherVersion is greater than the current environment + // version. + var versionGEThan = function versionGEThan (otherVersion) { + if (otherVersion === version) return true; + + if (typeof(otherVersion) === 'number' && typeof(version) === 'number') { + return otherVersion > version; + } + + // The versions have multiple components (i.e. 0.10.30) and + // must be compared piecewise. + // Note: I'm ignoring the case where one version has multiple + // components and the other doesn't. + var v1 = otherVersion.split('.'), + v2 = version.split('.'), + versionLength = (v1.length > v2.length ? v2 : v1).length; + + for (var i = 0; i < versionLength; ++i) { + if (parseInt(v1[i], 10) > parseInt(v2[i], 10)) { + return true; } } - function toDebugString(object) { - var components = []; - - if (typeof(object) === 'undefined') { - // noop - } - else if (object === null) { - components.push('NULL'); - } - else if (OTHelpers.isArray(object)) { - for (var i=0; i -1 || userAgent.indexOf('opr') > -1) { + name = 'Opera'; + + if (/opr\/([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) { + version = parseFloat( RegExp.$1 ); + } + + } else if (userAgent.indexOf('firefox') > -1) { + name = 'Firefox'; + + if (/firefox\/([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) { + version = parseFloat( RegExp.$1 ); + } + + } else if (appName === 'Microsoft Internet Explorer') { + // IE 10 and below + name = 'IE'; + + if (/msie ([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) { + version = parseFloat( RegExp.$1 ); + } + + } else if (appName === 'Netscape' && userAgent.indexOf('trident') > -1) { + // IE 11+ + + name = 'IE'; + + if (/trident\/.*rv:([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) { + version = parseFloat( RegExp.$1 ); + } + + } else if (userAgent.indexOf('chrome') > -1) { + name = 'Chrome'; + + if (/chrome\/([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) { + version = parseFloat( RegExp.$1 ); + } + + } else if ((navigatorVendor = window.navigator.vendor) && + navigatorVendor.toLowerCase().indexOf('apple') > -1) { + name = 'Safari'; + + if (/version\/([0-9]{1,}[\.0-9]{0,})/.exec(userAgent) !== null) { + version = parseFloat( RegExp.$1 ); + } + } + + return { + name: name, + version: version, + userAgent: window.navigator.userAgent, + iframeNeedsLoad: userAgent.indexOf('webkit') < 0, + versionGreaterThan: versionGEThan + }; + }(); + + + OTHelpers.env = env; + + OTHelpers.browser = function() { + return OTHelpers.env.name; + }; + + OTHelpers.browserVersion = function() { + return OTHelpers.env; + }; + +})(); +/*jshint browser:false, smarttabs:true*/ +/* global window, require */ + +// tb_require('../../helpers.js') +// tb_require('../environment.js') + +if (window.OTHelpers.env.name === 'Node') { + var request = require('request'); + + OTHelpers.request = function(url, options, callback) { + var completion = function(error, response, body) { + var event = {response: response, body: body}; + + // We need to detect things that Request considers a success, + // but we consider to be failures. + if (!error && response.statusCode >= 200 && + (response.statusCode < 300 || response.statusCode === 304) ) { + callback(null, event); + } else { + callback(error, event); + } + }; + + if (options.method.toLowerCase() === 'get') { + request.get(url, completion); + } + else { + request.post(url, options.body, completion); } }; - OTHelpers.useLogHelpers(OTHelpers); - OTHelpers.setLogLevel(OTHelpers.ERROR); + OTHelpers.getJSON = function(url, options, callback) { + var extendedHeaders = require('underscore').extend( + { + 'Accept': 'application/json' + }, + options.headers || {} + ); -})(window, window.OTHelpers); + request.get({ + url: url, + headers: extendedHeaders, + json: true + }, function(err, response) { + callback(err, response && response.body); + }); + }; +} +/*jshint browser:true, smarttabs:true*/ + +// tb_require('../../helpers.js') +// tb_require('../environment.js') + +function formatPostData(data) { //, contentType + // If it's a string, we assume it's properly encoded + if (typeof(data) === 'string') return data; + + var queryString = []; + + for (var key in data) { + queryString.push( + encodeURIComponent(key) + '=' + encodeURIComponent(data[key]) + ); + } + + return queryString.join('&').replace(/\+/g, '%20'); +} + +if (window.OTHelpers.env.name !== 'Node') { + + OTHelpers.xdomainRequest = function(url, options, callback) { + /*global XDomainRequest*/ + var xdr = new XDomainRequest(), + _options = options || {}, + _method = _options.method.toLowerCase(); + + if(!_method) { + callback(new Error('No HTTP method specified in options')); + return; + } + + _method = _method.toUpperCase(); + + if(!(_method === 'GET' || _method === 'POST')) { + callback(new Error('HTTP method can only be ')); + return; + } + + function done(err, event) { + xdr.onload = xdr.onerror = xdr.ontimeout = function() {}; + xdr = void 0; + callback(err, event); + } + + + xdr.onload = function() { + done(null, { + target: { + responseText: xdr.responseText, + headers: { + 'content-type': xdr.contentType + } + } + }); + }; + + xdr.onerror = function() { + done(new Error('XDomainRequest of ' + url + ' failed')); + }; + + xdr.ontimeout = function() { + done(new Error('XDomainRequest of ' + url + ' timed out')); + }; + + xdr.open(_method, url); + xdr.send(options.body && formatPostData(options.body)); + + }; + + OTHelpers.request = function(url, options, callback) { + var request = new XMLHttpRequest(), + _options = options || {}, + _method = _options.method; + + if(!_method) { + callback(new Error('No HTTP method specified in options')); + return; + } + + // Setup callbacks to correctly respond to success and error callbacks. This includes + // interpreting the responses HTTP status, which XmlHttpRequest seems to ignore + // by default. + if(callback) { + OTHelpers.on(request, 'load', function(event) { + var status = event.target.status; + + // We need to detect things that XMLHttpRequest considers a success, + // but we consider to be failures. + if ( status >= 200 && (status < 300 || status === 304) ) { + callback(null, event); + } else { + callback(event); + } + }); + + OTHelpers.on(request, 'error', callback); + } + + request.open(options.method, url, true); + + if (!_options.headers) _options.headers = {}; + + for (var name in _options.headers) { + request.setRequestHeader(name, _options.headers[name]); + } + + request.send(options.body && formatPostData(options.body)); + }; + + + OTHelpers.getJSON = function(url, options, callback) { + options = options || {}; + + var done = function(error, event) { + if(error) { + callback(error, event && event.target && event.target.responseText); + } else { + var response; + + try { + response = JSON.parse(event.target.responseText); + } catch(e) { + // Badly formed JSON + callback(e, event && event.target && event.target.responseText); + return; + } + + callback(null, response, event); + } + }; + + if(options.xdomainrequest) { + OTHelpers.xdomainRequest(url, { method: 'GET' }, done); + } else { + var extendedHeaders = OTHelpers.extend({ + 'Accept': 'application/json' + }, options.headers || {}); + + OTHelpers.get(url, OTHelpers.extend(options || {}, { + headers: extendedHeaders + }), done); + } + + }; + +} +/*jshint browser:true, smarttabs:true*/ + +// tb_require('../helpers.js') +// tb_require('./environment.js') + +OTHelpers.useLogHelpers = function(on){ + + // Log levels for OTLog.setLogLevel + on.DEBUG = 5; + on.LOG = 4; + on.INFO = 3; + on.WARN = 2; + on.ERROR = 1; + on.NONE = 0; + + var _logLevel = on.NONE, + _logs = [], + _canApplyConsole = true; + + try { + Function.prototype.bind.call(window.console.log, window.console); + } catch (err) { + _canApplyConsole = false; + } + + // Some objects can't be logged in the console, mostly these are certain + // types of native objects that are exposed to JS. This is only really a + // problem with IE, hence only the IE version does anything. + var makeLogArgumentsSafe = function(args) { return args; }; + + if (OTHelpers.env.name === 'IE') { + makeLogArgumentsSafe = function(args) { + return [toDebugString(prototypeSlice.apply(args))]; + }; + } + + // Generates a logging method for a particular method and log level. + // + // Attempts to handle the following cases: + // * the desired log method doesn't exist, call fallback (if available) instead + // * the console functionality isn't available because the developer tools (in IE) + // aren't open, call fallback (if available) + // * attempt to deal with weird IE hosted logging methods as best we can. + // + function generateLoggingMethod(method, level, fallback) { + return function() { + if (on.shouldLog(level)) { + var cons = window.console, + args = makeLogArgumentsSafe(arguments); + + // In IE, window.console may not exist if the developer tools aren't open + // This also means that cons and cons[method] can appear at any moment + // hence why we retest this every time. + if (cons && cons[method]) { + // the desired console method isn't a real object, which means + // that we can't use apply on it. We force it to be a real object + // using Function.bind, assuming that's available. + if (cons[method].apply || _canApplyConsole) { + if (!cons[method].apply) { + cons[method] = Function.prototype.bind.call(cons[method], cons); + } + + cons[method].apply(cons, args); + } + else { + // This isn't the same result as the above, but it's better + // than nothing. + cons[method](args); + } + } + else if (fallback) { + fallback.apply(on, args); + + // Skip appendToLogs, we delegate entirely to the fallback + return; + } + + appendToLogs(method, makeLogArgumentsSafe(arguments)); + } + }; + } + + on.log = generateLoggingMethod('log', on.LOG); + + // Generate debug, info, warn, and error logging methods, these all fallback to on.log + on.debug = generateLoggingMethod('debug', on.DEBUG, on.log); + on.info = generateLoggingMethod('info', on.INFO, on.log); + on.warn = generateLoggingMethod('warn', on.WARN, on.log); + on.error = generateLoggingMethod('error', on.ERROR, on.log); + + + on.setLogLevel = function(level) { + _logLevel = typeof(level) === 'number' ? level : 0; + on.debug('TB.setLogLevel(' + _logLevel + ')'); + return _logLevel; + }; + + on.getLogs = function() { + return _logs; + }; + + // Determine if the level is visible given the current logLevel. + on.shouldLog = function(level) { + return _logLevel >= level; + }; + + // Format the current time nicely for logging. Returns the current + // local time. + function formatDateStamp() { + var now = new Date(); + return now.toLocaleTimeString() + now.getMilliseconds(); + } + + function toJson(object) { + try { + return JSON.stringify(object); + } catch(e) { + return object.toString(); + } + } + + function toDebugString(object) { + var components = []; + + if (typeof(object) === 'undefined') { + // noop + } + else if (object === null) { + components.push('NULL'); + } + else if (OTHelpers.isArray(object)) { + for (var i=0; ion, once, and off * methods of objects that can dispatch events. * * @class EventDispatcher */ - OTHelpers.eventing = function(self, syncronous) { - var _events = {}; +OTHelpers.eventing = function(self, syncronous) { + var _events = {}; + // Call the defaultAction, passing args + function executeDefaultAction(defaultAction, args) { + if (!defaultAction) return; - // Call the defaultAction, passing args - function executeDefaultAction(defaultAction, args) { - if (!defaultAction) return; + defaultAction.apply(null, args.slice()); + } - defaultAction.apply(null, args.slice()); - } + // Execute each handler in +listeners+ with +args+. + // + // Each handler will be executed async. On completion the defaultAction + // handler will be executed with the args. + // + // @param [Array] listeners + // An array of functions to execute. Each will be passed args. + // + // @param [Array] args + // An array of arguments to execute each function in +listeners+ with. + // + // @param [String] name + // The name of this event. + // + // @param [Function, Null, Undefined] defaultAction + // An optional function to execute after every other handler. This will execute even + // if +listeners+ is empty. +defaultAction+ will be passed args as a normal + // handler would. + // + // @return Undefined + // + function executeListenersAsyncronously(name, args, defaultAction) { + var listeners = _events[name]; + if (!listeners || listeners.length === 0) return; - // Execute each handler in +listeners+ with +args+. - // - // Each handler will be executed async. On completion the defaultAction - // handler will be executed with the args. - // - // @param [Array] listeners - // An array of functions to execute. Each will be passed args. - // - // @param [Array] args - // An array of arguments to execute each function in +listeners+ with. - // - // @param [String] name - // The name of this event. - // - // @param [Function, Null, Undefined] defaultAction - // An optional function to execute after every other handler. This will execute even - // if +listeners+ is empty. +defaultAction+ will be passed args as a normal - // handler would. - // - // @return Undefined - // - function executeListenersAsyncronously(name, args, defaultAction) { - var listeners = _events[name]; - if (!listeners || listeners.length === 0) return; + var listenerAcks = listeners.length; - var listenerAcks = listeners.length; + OTHelpers.forEach(listeners, function(listener) { // , index + function filterHandlerAndContext(_listener) { + return _listener.context === listener.context && _listener.handler === listener.handler; + } - OTHelpers.forEach(listeners, function(listener) { // , index - function filterHandlerAndContext(_listener) { - return _listener.context === listener.context && _listener.handler === listener.handler; + // We run this asynchronously so that it doesn't interfere with execution if an + // error happens + OTHelpers.callAsync(function() { + try { + // have to check if the listener has not been removed + if (_events[name] && OTHelpers.some(_events[name], filterHandlerAndContext)) { + (listener.closure || listener.handler).apply(listener.context || null, args); + } } + finally { + listenerAcks--; - // We run this asynchronously so that it doesn't interfere with execution if an - // error happens - OTHelpers.callAsync(function() { - try { - // have to check if the listener has not been removed - if (_events[name] && OTHelpers.some(_events[name], filterHandlerAndContext)) { - (listener.closure || listener.handler).apply(listener.context || null, args); - } + if (listenerAcks === 0) { + executeDefaultAction(defaultAction, args); } - finally { - listenerAcks--; + } + }); + }); + } - if (listenerAcks === 0) { - executeDefaultAction(defaultAction, args); - } - } + + // This is identical to executeListenersAsyncronously except that handlers will + // be executed syncronously. + // + // On completion the defaultAction handler will be executed with the args. + // + // @param [Array] listeners + // An array of functions to execute. Each will be passed args. + // + // @param [Array] args + // An array of arguments to execute each function in +listeners+ with. + // + // @param [String] name + // The name of this event. + // + // @param [Function, Null, Undefined] defaultAction + // An optional function to execute after every other handler. This will execute even + // if +listeners+ is empty. +defaultAction+ will be passed args as a normal + // handler would. + // + // @return Undefined + // + function executeListenersSyncronously(name, args) { // defaultAction is not used + var listeners = _events[name]; + if (!listeners || listeners.length === 0) return; + + OTHelpers.forEach(listeners, function(listener) { // index + (listener.closure || listener.handler).apply(listener.context || null, args); + }); + } + + var executeListeners = syncronous === true ? + executeListenersSyncronously : executeListenersAsyncronously; + + + var removeAllListenersNamed = function (eventName, context) { + if (_events[eventName]) { + if (context) { + // We are removing by context, get only events that don't + // match that context + _events[eventName] = OTHelpers.filter(_events[eventName], function(listener){ + return listener.context !== context; }); - }); - } - - - // This is identical to executeListenersAsyncronously except that handlers will - // be executed syncronously. - // - // On completion the defaultAction handler will be executed with the args. - // - // @param [Array] listeners - // An array of functions to execute. Each will be passed args. - // - // @param [Array] args - // An array of arguments to execute each function in +listeners+ with. - // - // @param [String] name - // The name of this event. - // - // @param [Function, Null, Undefined] defaultAction - // An optional function to execute after every other handler. This will execute even - // if +listeners+ is empty. +defaultAction+ will be passed args as a normal - // handler would. - // - // @return Undefined - // - function executeListenersSyncronously(name, args) { // defaultAction is not used - var listeners = _events[name]; - if (!listeners || listeners.length === 0) return; - - OTHelpers.forEach(listeners, function(listener) { // index - (listener.closure || listener.handler).apply(listener.context || null, args); - }); - } - - var executeListeners = syncronous === true ? - executeListenersSyncronously : executeListenersAsyncronously; - - - var removeAllListenersNamed = function (eventName, context) { - if (_events[eventName]) { - if (context) { - // We are removing by context, get only events that don't - // match that context - _events[eventName] = OTHelpers.filter(_events[eventName], function(listener){ - return listener.context !== context; - }); - } - else { - delete _events[eventName]; - } - } - }; - - var addListeners = OTHelpers.bind(function (eventNames, handler, context, closure) { - var listener = {handler: handler}; - if (context) listener.context = context; - if (closure) listener.closure = closure; - - OTHelpers.forEach(eventNames, function(name) { - if (!_events[name]) _events[name] = []; - _events[name].push(listener); - var addedListener = name + ':added'; - if (_events[addedListener]) { - executeListeners(addedListener, [_events[name].length]); - } - }); - }, self); - - - var removeListeners = function (eventNames, handler, context) { - function filterHandlerAndContext(listener) { - return !(listener.handler === handler && listener.context === context); - } - - OTHelpers.forEach(eventNames, OTHelpers.bind(function(name) { - if (_events[name]) { - _events[name] = OTHelpers.filter(_events[name], filterHandlerAndContext); - if (_events[name].length === 0) delete _events[name]; - var removedListener = name + ':removed'; - if (_events[ removedListener]) { - executeListeners(removedListener, [_events[name] ? _events[name].length : 0]); - } - } - }, self)); - - }; - - // Execute any listeners bound to the +event+ Event. - // - // Each handler will be executed async. On completion the defaultAction - // handler will be executed with the args. - // - // @param [Event] event - // An Event object. - // - // @param [Function, Null, Undefined] defaultAction - // An optional function to execute after every other handler. This will execute even - // if there are listeners bound to this event. +defaultAction+ will be passed - // args as a normal handler would. - // - // @return this - // - self.dispatchEvent = function(event, defaultAction) { - if (!event.type) { - OTHelpers.error('OTHelpers.Eventing.dispatchEvent: Event has no type'); - OTHelpers.error(event); - - throw new Error('OTHelpers.Eventing.dispatchEvent: Event has no type'); - } - - if (!event.target) { - event.target = this; - } - - if (!_events[event.type] || _events[event.type].length === 0) { - executeDefaultAction(defaultAction, [event]); - return; - } - - executeListeners(event.type, [event], defaultAction); - - return this; - }; - - // Execute each handler for the event called +name+. - // - // Each handler will be executed async, and any exceptions that they throw will - // be caught and logged - // - // How to pass these? - // * defaultAction - // - // @example - // foo.on('bar', function(name, message) { - // alert("Hello " + name + ": " + message); - // }); - // - // foo.trigger('OpenTok', 'asdf'); // -> Hello OpenTok: asdf - // - // - // @param [String] eventName - // The name of this event. - // - // @param [Array] arguments - // Any additional arguments beyond +eventName+ will be passed to the handlers. - // - // @return this - // - self.trigger = function(eventName) { - if (!_events[eventName] || _events[eventName].length === 0) { - return; - } - - var args = Array.prototype.slice.call(arguments); - - // Remove the eventName arg - args.shift(); - - executeListeners(eventName, args); - - return this; - }; - - /** - * Adds an event handler function for one or more events. - * - *

- * The following code adds an event handler for one event: - *

- * - *
-    * obj.on("eventName", function (event) {
-    *     // This is the event handler.
-    * });
-    * 
- * - *

If you pass in multiple event names and a handler method, the handler is - * registered for each of those events:

- * - *
-    * obj.on("eventName1 eventName2",
-    *        function (event) {
-    *            // This is the event handler.
-    *        });
-    * 
- * - *

You can also pass in a third context parameter (which is optional) to - * define the value of this in the handler method:

- * - *
obj.on("eventName",
-    *        function (event) {
-    *            // This is the event handler.
-    *        },
-    *        obj);
-    * 
- * - *

- * The method also supports an alternate syntax, in which the first parameter is an object - * that is a hash map of event names and handler functions and the second parameter (optional) - * is the context for this in each handler: - *

- *
-    * obj.on(
-    *    {
-    *       eventName1: function (event) {
-    *               // This is the handler for eventName1.
-    *           },
-    *       eventName2:  function (event) {
-    *               // This is the handler for eventName2.
-    *           }
-    *    },
-    *    obj);
-    * 
- * - *

- * If you do not add a handler for an event, the event is ignored locally. - *

- * - * @param {String} type The string identifying the type of event. You can specify multiple event - * names in this string, separating them with a space. The event handler will process each of - * the events. - * @param {Function} handler The handler function to process the event. This function takes - * the event object as a parameter. - * @param {Object} context (Optional) Defines the value of this in the event - * handler function. - * - * @returns {EventDispatcher} The EventDispatcher object. - * - * @memberOf EventDispatcher - * @method #on - * @see off() - * @see once() - * @see Events - */ - self.on = function(eventNames, handlerOrContext, context) { - if (typeof(eventNames) === 'string' && handlerOrContext) { - addListeners(eventNames.split(' '), handlerOrContext, context); } else { - for (var name in eventNames) { - if (eventNames.hasOwnProperty(name)) { - addListeners([name], eventNames[name], handlerOrContext); - } + delete _events[eventName]; + } + } + }; + + var addListeners = OTHelpers.bind(function (eventNames, handler, context, closure) { + var listener = {handler: handler}; + if (context) listener.context = context; + if (closure) listener.closure = closure; + + OTHelpers.forEach(eventNames, function(name) { + if (!_events[name]) _events[name] = []; + _events[name].push(listener); + var addedListener = name + ':added'; + if (_events[addedListener]) { + executeListeners(addedListener, [_events[name].length]); + } + }); + }, self); + + + var removeListeners = function (eventNames, handler, context) { + function filterHandlerAndContext(listener) { + return !(listener.handler === handler && listener.context === context); + } + + OTHelpers.forEach(eventNames, OTHelpers.bind(function(name) { + if (_events[name]) { + _events[name] = OTHelpers.filter(_events[name], filterHandlerAndContext); + if (_events[name].length === 0) delete _events[name]; + var removedListener = name + ':removed'; + if (_events[ removedListener]) { + executeListeners(removedListener, [_events[name] ? _events[name].length : 0]); } } + }, self)); - return this; - }; + }; - /** - * Removes an event handler or handlers. - * - *

If you pass in one event name and a handler method, the handler is removed for that - * event:

- * - *
obj.off("eventName", eventHandler);
- * - *

If you pass in multiple event names and a handler method, the handler is removed for - * those events:

- * - *
obj.off("eventName1 eventName2", eventHandler);
- * - *

If you pass in an event name (or names) and no handler method, all handlers are - * removed for those events:

- * - *
obj.off("event1Name event2Name");
- * - *

If you pass in no arguments, all event handlers are removed for all events - * dispatched by the object:

- * - *
obj.off();
- * - *

- * The method also supports an alternate syntax, in which the first parameter is an object that - * is a hash map of event names and handler functions and the second parameter (optional) is - * the context for this in each handler: - *

- *
-    * obj.off(
-    *    {
-    *       eventName1: event1Handler,
-    *       eventName2: event2Handler
-    *    });
-    * 
- * - * @param {String} type (Optional) The string identifying the type of event. You can - * use a space to specify multiple events, as in "accessAllowed accessDenied - * accessDialogClosed". If you pass in no type value (or other arguments), - * all event handlers are removed for the object. - * @param {Function} handler (Optional) The event handler function to remove. The handler - * must be the same function object as was passed into on(). Be careful with - * helpers like bind() that return a new function when called. If you pass in - * no handler, all event handlers are removed for the specified event - * type. - * @param {Object} context (Optional) If you specify a context, the event handler - * is removed for all specified events and handlers that use the specified context. (The - * context must match the context passed into on().) - * - * @returns {Object} The object that dispatched the event. - * - * @memberOf EventDispatcher - * @method #off - * @see on() - * @see once() - * @see Events - */ - self.off = function(eventNames, handlerOrContext, context) { - if (typeof eventNames === 'string') { - if (handlerOrContext && OTHelpers.isFunction(handlerOrContext)) { - removeListeners(eventNames.split(' '), handlerOrContext, context); + // Execute any listeners bound to the +event+ Event. + // + // Each handler will be executed async. On completion the defaultAction + // handler will be executed with the args. + // + // @param [Event] event + // An Event object. + // + // @param [Function, Null, Undefined] defaultAction + // An optional function to execute after every other handler. This will execute even + // if there are listeners bound to this event. +defaultAction+ will be passed + // args as a normal handler would. + // + // @return this + // + self.dispatchEvent = function(event, defaultAction) { + if (!event.type) { + OTHelpers.error('OTHelpers.Eventing.dispatchEvent: Event has no type'); + OTHelpers.error(event); + + throw new Error('OTHelpers.Eventing.dispatchEvent: Event has no type'); + } + + if (!event.target) { + event.target = this; + } + + if (!_events[event.type] || _events[event.type].length === 0) { + executeDefaultAction(defaultAction, [event]); + return; + } + + executeListeners(event.type, [event], defaultAction); + + return this; + }; + + // Execute each handler for the event called +name+. + // + // Each handler will be executed async, and any exceptions that they throw will + // be caught and logged + // + // How to pass these? + // * defaultAction + // + // @example + // foo.on('bar', function(name, message) { + // alert("Hello " + name + ": " + message); + // }); + // + // foo.trigger('OpenTok', 'asdf'); // -> Hello OpenTok: asdf + // + // + // @param [String] eventName + // The name of this event. + // + // @param [Array] arguments + // Any additional arguments beyond +eventName+ will be passed to the handlers. + // + // @return this + // + self.trigger = function(eventName) { + if (!_events[eventName] || _events[eventName].length === 0) { + return; + } + + var args = prototypeSlice.call(arguments); + + // Remove the eventName arg + args.shift(); + + executeListeners(eventName, args); + + return this; + }; + + /** + * Adds an event handler function for one or more events. + * + *

+ * The following code adds an event handler for one event: + *

+ * + *
+  * obj.on("eventName", function (event) {
+  *     // This is the event handler.
+  * });
+  * 
+ * + *

If you pass in multiple event names and a handler method, the handler is + * registered for each of those events:

+ * + *
+  * obj.on("eventName1 eventName2",
+  *        function (event) {
+  *            // This is the event handler.
+  *        });
+  * 
+ * + *

You can also pass in a third context parameter (which is optional) to + * define the value of this in the handler method:

+ * + *
obj.on("eventName",
+  *        function (event) {
+  *            // This is the event handler.
+  *        },
+  *        obj);
+  * 
+ * + *

+ * The method also supports an alternate syntax, in which the first parameter is an object + * that is a hash map of event names and handler functions and the second parameter (optional) + * is the context for this in each handler: + *

+ *
+  * obj.on(
+  *    {
+  *       eventName1: function (event) {
+  *               // This is the handler for eventName1.
+  *           },
+  *       eventName2:  function (event) {
+  *               // This is the handler for eventName2.
+  *           }
+  *    },
+  *    obj);
+  * 
+ * + *

+ * If you do not add a handler for an event, the event is ignored locally. + *

+ * + * @param {String} type The string identifying the type of event. You can specify multiple event + * names in this string, separating them with a space. The event handler will process each of + * the events. + * @param {Function} handler The handler function to process the event. This function takes + * the event object as a parameter. + * @param {Object} context (Optional) Defines the value of this in the event + * handler function. + * + * @returns {EventDispatcher} The EventDispatcher object. + * + * @memberOf EventDispatcher + * @method #on + * @see off() + * @see once() + * @see Events + */ + self.on = function(eventNames, handlerOrContext, context) { + if (typeof(eventNames) === 'string' && handlerOrContext) { + addListeners(eventNames.split(' '), handlerOrContext, context); + } + else { + for (var name in eventNames) { + if (eventNames.hasOwnProperty(name)) { + addListeners([name], eventNames[name], handlerOrContext); } - else { - OTHelpers.forEach(eventNames.split(' '), function(name) { - removeAllListenersNamed(name, handlerOrContext); - }, this); + } + } + + return this; + }; + + /** + * Removes an event handler or handlers. + * + *

If you pass in one event name and a handler method, the handler is removed for that + * event:

+ * + *
obj.off("eventName", eventHandler);
+ * + *

If you pass in multiple event names and a handler method, the handler is removed for + * those events:

+ * + *
obj.off("eventName1 eventName2", eventHandler);
+ * + *

If you pass in an event name (or names) and no handler method, all handlers are + * removed for those events:

+ * + *
obj.off("event1Name event2Name");
+ * + *

If you pass in no arguments, all event handlers are removed for all events + * dispatched by the object:

+ * + *
obj.off();
+ * + *

+ * The method also supports an alternate syntax, in which the first parameter is an object that + * is a hash map of event names and handler functions and the second parameter (optional) is + * the context for this in each handler: + *

+ *
+  * obj.off(
+  *    {
+  *       eventName1: event1Handler,
+  *       eventName2: event2Handler
+  *    });
+  * 
+ * + * @param {String} type (Optional) The string identifying the type of event. You can + * use a space to specify multiple events, as in "accessAllowed accessDenied + * accessDialogClosed". If you pass in no type value (or other arguments), + * all event handlers are removed for the object. + * @param {Function} handler (Optional) The event handler function to remove. The handler + * must be the same function object as was passed into on(). Be careful with + * helpers like bind() that return a new function when called. If you pass in + * no handler, all event handlers are removed for the specified event + * type. + * @param {Object} context (Optional) If you specify a context, the event handler + * is removed for all specified events and handlers that use the specified context. (The + * context must match the context passed into on().) + * + * @returns {Object} The object that dispatched the event. + * + * @memberOf EventDispatcher + * @method #off + * @see on() + * @see once() + * @see Events + */ + self.off = function(eventNames, handlerOrContext, context) { + if (typeof eventNames === 'string') { + if (handlerOrContext && OTHelpers.isFunction(handlerOrContext)) { + removeListeners(eventNames.split(' '), handlerOrContext, context); + } + else { + OTHelpers.forEach(eventNames.split(' '), function(name) { + removeAllListenersNamed(name, handlerOrContext); + }, this); + } + + } else if (!eventNames) { + // remove all bound events + _events = {}; + + } else { + for (var name in eventNames) { + if (eventNames.hasOwnProperty(name)) { + removeListeners([name], eventNames[name], handlerOrContext); } + } + } - } else if (!eventNames) { - // remove all bound events - _events = {}; + return this; + }; + + /** + * Adds an event handler function for one or more events. Once the handler is called, + * the specified handler method is removed as a handler for this event. (When you use + * the on() method to add an event handler, the handler is not + * removed when it is called.) The once() method is the equivilent of + * calling the on() + * method and calling off() the first time the handler is invoked. + * + *

+ * The following code adds a one-time event handler for the accessAllowed event: + *

+ * + *
+  * obj.once("eventName", function (event) {
+  *    // This is the event handler.
+  * });
+  * 
+ * + *

If you pass in multiple event names and a handler method, the handler is registered + * for each of those events:

+ * + *
obj.once("eventName1 eventName2"
+  *          function (event) {
+  *              // This is the event handler.
+  *          });
+  * 
+ * + *

You can also pass in a third context parameter (which is optional) to define + * the value of + * this in the handler method:

+ * + *
obj.once("eventName",
+  *          function (event) {
+  *              // This is the event handler.
+  *          },
+  *          obj);
+  * 
+ * + *

+ * The method also supports an alternate syntax, in which the first parameter is an object that + * is a hash map of event names and handler functions and the second parameter (optional) is the + * context for this in each handler: + *

+ *
+  * obj.once(
+  *    {
+  *       eventName1: function (event) {
+  *                  // This is the event handler for eventName1.
+  *           },
+  *       eventName2:  function (event) {
+  *                  // This is the event handler for eventName1.
+  *           }
+  *    },
+  *    obj);
+  * 
+ * + * @param {String} type The string identifying the type of event. You can specify multiple + * event names in this string, separating them with a space. The event handler will process + * the first occurence of the events. After the first event, the handler is removed (for + * all specified events). + * @param {Function} handler The handler function to process the event. This function takes + * the event object as a parameter. + * @param {Object} context (Optional) Defines the value of this in the event + * handler function. + * + * @returns {Object} The object that dispatched the event. + * + * @memberOf EventDispatcher + * @method #once + * @see on() + * @see off() + * @see Events + */ + self.once = function(eventNames, handler, context) { + var names = eventNames.split(' '), + fun = OTHelpers.bind(function() { + var result = handler.apply(context || null, arguments); + removeListeners(names, handler, context); + + return result; + }, this); + + addListeners(names, handler, context, fun); + return this; + }; + + + /** + * Deprecated; use on() or once() instead. + *

+ * This method registers a method as an event listener for a specific event. + *

+ * + *

+ * If a handler is not registered for an event, the event is ignored locally. If the + * event listener function does not exist, the event is ignored locally. + *

+ *

+ * Throws an exception if the listener name is invalid. + *

+ * + * @param {String} type The string identifying the type of event. + * + * @param {Function} listener The function to be invoked when the object dispatches the event. + * + * @param {Object} context (Optional) Defines the value of this in the event + * handler function. + * + * @memberOf EventDispatcher + * @method #addEventListener + * @see on() + * @see once() + * @see Events + */ + // See 'on' for usage. + // @depreciated will become a private helper function in the future. + self.addEventListener = function(eventName, handler, context) { + OTHelpers.warn('The addEventListener() method is deprecated. Use on() or once() instead.'); + addListeners([eventName], handler, context); + }; + + + /** + * Deprecated; use on() or once() instead. + *

+ * Removes an event listener for a specific event. + *

+ * + *

+ * Throws an exception if the listener name is invalid. + *

+ * + * @param {String} type The string identifying the type of event. + * + * @param {Function} listener The event listener function to remove. + * + * @param {Object} context (Optional) If you specify a context, the event + * handler is removed for all specified events and event listeners that use the specified + context. (The context must match the context passed into + * addEventListener().) + * + * @memberOf EventDispatcher + * @method #removeEventListener + * @see off() + * @see Events + */ + // See 'off' for usage. + // @depreciated will become a private helper function in the future. + self.removeEventListener = function(eventName, handler, context) { + OTHelpers.warn('The removeEventListener() method is deprecated. Use off() instead.'); + removeListeners([eventName], handler, context); + }; + + + return self; +}; + +OTHelpers.eventing.Event = function() { + return function (type, cancelable) { + this.type = type; + this.cancelable = cancelable !== undefined ? cancelable : true; + + var _defaultPrevented = false; + + this.preventDefault = function() { + if (this.cancelable) { + _defaultPrevented = true; } else { - for (var name in eventNames) { - if (eventNames.hasOwnProperty(name)) { - removeListeners([name], eventNames[name], handlerOrContext); - } + OTHelpers.warn('Event.preventDefault :: Trying to preventDefault ' + + 'on an Event that isn\'t cancelable'); + } + }; + + this.isDefaultPrevented = function() { + return _defaultPrevented; + }; + }; +}; + +/*jshint browser:true, smarttabs:true */ + +// tb_require('../helpers.js') +// tb_require('./callbacks.js') +// tb_require('./dom_events.js') + +OTHelpers.createElement = function(nodeName, attributes, children, doc) { + var element = (doc || document).createElement(nodeName); + + if (attributes) { + for (var name in attributes) { + if (typeof(attributes[name]) === 'object') { + if (!element[name]) element[name] = {}; + + var subAttrs = attributes[name]; + for (var n in subAttrs) { + element[name][n] = subAttrs[n]; } } + else if (name === 'className') { + element.className = attributes[name]; + } + else { + element.setAttribute(name, attributes[name]); + } + } + } - return this; - }; - - - /** - * Adds an event handler function for one or more events. Once the handler is called, - * the specified handler method is removed as a handler for this event. (When you use - * the on() method to add an event handler, the handler is not - * removed when it is called.) The once() method is the equivilent of - * calling the on() - * method and calling off() the first time the handler is invoked. - * - *

- * The following code adds a one-time event handler for the accessAllowed event: - *

- * - *
-    * obj.once("eventName", function (event) {
-    *    // This is the event handler.
-    * });
-    * 
- * - *

If you pass in multiple event names and a handler method, the handler is registered - * for each of those events:

- * - *
obj.once("eventName1 eventName2"
-    *          function (event) {
-    *              // This is the event handler.
-    *          });
-    * 
- * - *

You can also pass in a third context parameter (which is optional) to define - * the value of - * this in the handler method:

- * - *
obj.once("eventName",
-    *          function (event) {
-    *              // This is the event handler.
-    *          },
-    *          obj);
-    * 
- * - *

- * The method also supports an alternate syntax, in which the first parameter is an object that - * is a hash map of event names and handler functions and the second parameter (optional) is the - * context for this in each handler: - *

- *
-    * obj.once(
-    *    {
-    *       eventName1: function (event) {
-    *                  // This is the event handler for eventName1.
-    *           },
-    *       eventName2:  function (event) {
-    *                  // This is the event handler for eventName1.
-    *           }
-    *    },
-    *    obj);
-    * 
- * - * @param {String} type The string identifying the type of event. You can specify multiple - * event names in this string, separating them with a space. The event handler will process - * the first occurence of the events. After the first event, the handler is removed (for - * all specified events). - * @param {Function} handler The handler function to process the event. This function takes - * the event object as a parameter. - * @param {Object} context (Optional) Defines the value of this in the event - * handler function. - * - * @returns {Object} The object that dispatched the event. - * - * @memberOf EventDispatcher - * @method #once - * @see on() - * @see off() - * @see Events - */ - self.once = function(eventNames, handler, context) { - var names = eventNames.split(' '), - fun = OTHelpers.bind(function() { - var result = handler.apply(context || null, arguments); - removeListeners(names, handler, context); - - return result; - }, this); - - addListeners(names, handler, context, fun); - return this; - }; - - - /** - * Deprecated; use on() or once() instead. - *

- * This method registers a method as an event listener for a specific event. - *

- * - *

- * If a handler is not registered for an event, the event is ignored locally. If the - * event listener function does not exist, the event is ignored locally. - *

- *

- * Throws an exception if the listener name is invalid. - *

- * - * @param {String} type The string identifying the type of event. - * - * @param {Function} listener The function to be invoked when the object dispatches the event. - * - * @param {Object} context (Optional) Defines the value of this in the event - * handler function. - * - * @memberOf EventDispatcher - * @method #addEventListener - * @see on() - * @see once() - * @see Events - */ - // See 'on' for usage. - // @depreciated will become a private helper function in the future. - self.addEventListener = function(eventName, handler, context) { - OTHelpers.warn('The addEventListener() method is deprecated. Use on() or once() instead.'); - addListeners([eventName], handler, context); - }; - - - /** - * Deprecated; use on() or once() instead. - *

- * Removes an event listener for a specific event. - *

- * - *

- * Throws an exception if the listener name is invalid. - *

- * - * @param {String} type The string identifying the type of event. - * - * @param {Function} listener The event listener function to remove. - * - * @param {Object} context (Optional) If you specify a context, the event - * handler is removed for all specified events and event listeners that use the specified - context. (The context must match the context passed into - * addEventListener().) - * - * @memberOf EventDispatcher - * @method #removeEventListener - * @see off() - * @see Events - */ - // See 'off' for usage. - // @depreciated will become a private helper function in the future. - self.removeEventListener = function(eventName, handler, context) { - OTHelpers.warn('The removeEventListener() method is deprecated. Use off() instead.'); - removeListeners([eventName], handler, context); - }; - - - - return self; + var setChildren = function(child) { + if(typeof child === 'string') { + element.innerHTML = element.innerHTML + child; + } else { + element.appendChild(child); + } }; - OTHelpers.eventing.Event = function() { + if($.isArray(children)) { + $.forEach(children, setChildren); + } else if(children) { + setChildren(children); + } - return function (type, cancelable) { - this.type = type; - this.cancelable = cancelable !== undefined ? cancelable : true; + return element; +}; - var _defaultPrevented = false; +OTHelpers.createButton = function(innerHTML, attributes, events) { + var button = $.createElement('button', attributes, innerHTML); - this.preventDefault = function() { - if (this.cancelable) { - _defaultPrevented = true; - } else { - OTHelpers.warn('Event.preventDefault :: Trying to preventDefault ' + - 'on an Event that isn\'t cancelable'); - } - }; + if (events) { + for (var name in events) { + if (events.hasOwnProperty(name)) { + $.on(button, name, events[name]); + } + } - this.isDefaultPrevented = function() { - return _defaultPrevented; - }; - }; + button._boundEvents = events; + } - }; - -})(window, window.OTHelpers); - -/*jshint browser:true, smarttabs:true*/ + return button; +}; +/*jshint browser:true, smarttabs:true */ // tb_require('../helpers.js') // tb_require('./callbacks.js') // DOM helpers -(function(window, OTHelpers, undefined) { - OTHelpers.isElementNode = function(node) { - return node && typeof node === 'object' && node.nodeType === 1; - }; +ElementCollection.prototype.appendTo = function(parentElement) { + if (!parentElement) throw new Error('appendTo requires a DOMElement to append to.'); - // Returns true if the client supports element.classList - OTHelpers.supportsClassList = function() { - var hasSupport = (typeof document !== 'undefined') && - ('classList' in document.createElement('a')); + return this.forEach(parentElement.appendChild.bind(parentElement)); +}; - OTHelpers.supportsClassList = function() { return hasSupport; }; +ElementCollection.prototype.after = function(prevElement) { + if (!prevElement) throw new Error('after requires a DOMElement to insert after'); - return hasSupport; - }; + return this.forEach(function(element) { + if (element.parentElement) { + if (prevElement !== element.parentNode.lastChild) { + element.parentElement.before(element, prevElement); + } + else { + element.parentElement.appendChild(element); + } + } + }); +}; - OTHelpers.removeElement = function(element) { - if (element && element.parentNode) { +ElementCollection.prototype.before = function(nextElement) { + if (!nextElement) throw new Error('before requires a DOMElement to insert before'); + + return this.forEach(function(element) { + if (element.parentElement) { + element.parentElement.before(element, nextElement); + } + }); +}; + +ElementCollection.prototype.remove = function () { + return this.forEach(function(element) { + if (element.parentNode) { element.parentNode.removeChild(element); } - }; - - OTHelpers.removeElementById = function(elementId) { - /*jshint newcap:false */ - this.removeElement(OTHelpers(elementId)); - }; - - OTHelpers.removeElementsByType = function(parentElem, type) { - if (!parentElem) return; - - var elements = parentElem.getElementsByTagName(type); + }); +}; +ElementCollection.prototype.empty = function () { + return this.forEach(function(element) { // elements is a "live" NodesList collection. Meaning that the collection // itself will be mutated as we remove elements from the DOM. This means // that "while there are still elements" is safer than "iterate over each // element" as the collection length and the elements indices will be modified // with each iteration. - while (elements.length) { - parentElem.removeChild(elements[0]); - } - }; - - OTHelpers.emptyElement = function(element) { while (element.firstChild) { element.removeChild(element.firstChild); } - return element; - }; - - OTHelpers.createElement = function(nodeName, attributes, children, doc) { - var element = (doc || document).createElement(nodeName); - - if (attributes) { - for (var name in attributes) { - if (typeof(attributes[name]) === 'object') { - if (!element[name]) element[name] = {}; - - var subAttrs = attributes[name]; - for (var n in subAttrs) { - element[name][n] = subAttrs[n]; - } - } - else if (name === 'className') { - element.className = attributes[name]; - } - else { - element.setAttribute(name, attributes[name]); - } - } - } - - var setChildren = function(child) { - if(typeof child === 'string') { - element.innerHTML = element.innerHTML + child; - } else { - element.appendChild(child); - } - }; - - if(OTHelpers.isArray(children)) { - OTHelpers.forEach(children, setChildren); - } else if(children) { - setChildren(children); - } - - return element; - }; - - OTHelpers.createButton = function(innerHTML, attributes, events) { - var button = OTHelpers.createElement('button', attributes, innerHTML); - - if (events) { - for (var name in events) { - if (events.hasOwnProperty(name)) { - OTHelpers.on(button, name, events[name]); - } - } - - button._boundEvents = events; - } - - return button; - }; + }); +}; - // Detects when an element is not part of the document flow because - // it or one of it's ancesters has display:none. - OTHelpers.isDisplayNone = function(element) { +// Detects when an element is not part of the document flow because +// it or one of it's ancesters has display:none. +ElementCollection.prototype.isDisplayNone = function() { + return this.some(function(element) { if ( (element.offsetWidth === 0 || element.offsetHeight === 0) && - OTHelpers.css(element, 'display') === 'none') return true; + $(element).css('display') === 'none') return true; if (element.parentNode && element.parentNode.style) { - return OTHelpers.isDisplayNone(element.parentNode); + return $(element.parentNode).isDisplayNone(); } + }); +}; - return false; - }; +ElementCollection.prototype.findElementWithDisplayNone = function(element) { + return $.findElementWithDisplayNone(element); +}; - OTHelpers.findElementWithDisplayNone = function(element) { - if ( (element.offsetWidth === 0 || element.offsetHeight === 0) && - OTHelpers.css(element, 'display') === 'none') return element; - if (element.parentNode && element.parentNode.style) { - return OTHelpers.findElementWithDisplayNone(element.parentNode); - } - return null; - }; +OTHelpers.isElementNode = function(node) { + return node && typeof node === 'object' && node.nodeType === 1; +}; - function objectHasProperties(obj) { - for (var key in obj) { - if (obj.hasOwnProperty(key)) return true; - } - return false; + +// @remove +OTHelpers.removeElement = function(element) { + $(element).remove(); +}; + +// @remove +OTHelpers.removeElementById = function(elementId) { + return $('#'+elementId).remove(); +}; + +// @remove +OTHelpers.removeElementsByType = function(parentElem, type) { + return $(type, parentElem).remove(); +}; + +// @remove +OTHelpers.emptyElement = function(element) { + return $(element).empty(); +}; + + + + + +// @remove +OTHelpers.isDisplayNone = function(element) { + return $(element).isDisplayNone(); +}; + +OTHelpers.findElementWithDisplayNone = function(element) { + if ( (element.offsetWidth === 0 || element.offsetHeight === 0) && + $.css(element, 'display') === 'none') return element; + + if (element.parentNode && element.parentNode.style) { + return $.findElementWithDisplayNone(element.parentNode); } - - // Allows an +onChange+ callback to be triggered when specific style properties - // of +element+ are notified. The callback accepts a single parameter, which is - // a hash where the keys are the style property that changed and the values are - // an array containing the old and new values ([oldValue, newValue]). - // - // Width and Height changes while the element is display: none will not be - // fired until such time as the element becomes visible again. - // - // This function returns the MutationObserver itself. Once you no longer wish - // to observe the element you should call disconnect on the observer. - // - // Observing changes: - // // observe changings to the width and height of object - // dimensionsObserver = OTHelpers.observeStyleChanges(object, - // ['width', 'height'], function(changeSet) { - // OT.debug("The new width and height are " + - // changeSet.width[1] + ',' + changeSet.height[1]); - // }); - // - // Cleaning up - // // stop observing changes - // dimensionsObserver.disconnect(); - // dimensionsObserver = null; - // - OTHelpers.observeStyleChanges = function(element, stylesToObserve, onChange) { - var oldStyles = {}; - - var getStyle = function getStyle(style) { - switch (style) { - case 'width': - return OTHelpers.width(element); - - case 'height': - return OTHelpers.height(element); - - default: - return OTHelpers.css(element); - } - }; - - // get the inital values - OTHelpers.forEach(stylesToObserve, function(style) { - oldStyles[style] = getStyle(style); - }); - - var observer = new MutationObserver(function(mutations) { - var changeSet = {}; - - OTHelpers.forEach(mutations, function(mutation) { - if (mutation.attributeName !== 'style') return; - - var isHidden = OTHelpers.isDisplayNone(element); - - OTHelpers.forEach(stylesToObserve, function(style) { - if(isHidden && (style === 'width' || style === 'height')) return; - - var newValue = getStyle(style); - - if (newValue !== oldStyles[style]) { - changeSet[style] = [oldStyles[style], newValue]; - oldStyles[style] = newValue; - } - }); - }); - - if (objectHasProperties(changeSet)) { - // Do this after so as to help avoid infinite loops of mutations. - OTHelpers.callAsync(function() { - onChange.call(null, changeSet); - }); - } - }); - - observer.observe(element, { - attributes:true, - attributeFilter: ['style'], - childList:false, - characterData:false, - subtree:false - }); - - return observer; - }; - - - // trigger the +onChange+ callback whenever - // 1. +element+ is removed - // 2. or an immediate child of +element+ is removed. - // - // This function returns the MutationObserver itself. Once you no longer wish - // to observe the element you should call disconnect on the observer. - // - // Observing changes: - // // observe changings to the width and height of object - // nodeObserver = OTHelpers.observeNodeOrChildNodeRemoval(object, function(removedNodes) { - // OT.debug("Some child nodes were removed"); - // OTHelpers.forEach(removedNodes, function(node) { - // OT.debug(node); - // }); - // }); - // - // Cleaning up - // // stop observing changes - // nodeObserver.disconnect(); - // nodeObserver = null; - // - OTHelpers.observeNodeOrChildNodeRemoval = function(element, onChange) { - var observer = new MutationObserver(function(mutations) { - var removedNodes = []; - - OTHelpers.forEach(mutations, function(mutation) { - if (mutation.removedNodes.length) { - removedNodes = removedNodes.concat(Array.prototype.slice.call(mutation.removedNodes)); - } - }); - - if (removedNodes.length) { - // Do this after so as to help avoid infinite loops of mutations. - OTHelpers.callAsync(function() { - onChange(removedNodes); - }); - } - }); - - observer.observe(element, { - attributes:false, - childList:true, - characterData:false, - subtree:true - }); - - return observer; - }; - -})(window, window.OTHelpers); + return null; +}; /*jshint browser:true, smarttabs:true*/ // tb_require('../helpers.js') +// tb_require('./environment.js') // tb_require('./dom.js') -(function(window, OTHelpers, undefined) { +OTHelpers.Modal = function(options) { - OTHelpers.Modal = function(options) { + OTHelpers.eventing(this, true); - OTHelpers.eventing(this, true); + var callback = arguments[arguments.length - 1]; - var callback = arguments[arguments.length - 1]; + if(!OTHelpers.isFunction(callback)) { + throw new Error('OTHelpers.Modal2 must be given a callback'); + } - if(!OTHelpers.isFunction(callback)) { - throw new Error('OTHelpers.Modal2 must be given a callback'); - } + if(arguments.length < 2) { + options = {}; + } - if(arguments.length < 2) { - options = {}; - } + var domElement = document.createElement('iframe'); - var domElement = document.createElement('iframe'); + domElement.id = options.id || OTHelpers.uuid(); + domElement.style.position = 'absolute'; + domElement.style.position = 'fixed'; + domElement.style.height = '100%'; + domElement.style.width = '100%'; + domElement.style.top = '0px'; + domElement.style.left = '0px'; + domElement.style.right = '0px'; + domElement.style.bottom = '0px'; + domElement.style.zIndex = 1000; + domElement.style.border = '0'; - domElement.id = options.id || OTHelpers.uuid(); - domElement.style.position = 'absolute'; - domElement.style.position = 'fixed'; - domElement.style.height = '100%'; - domElement.style.width = '100%'; - domElement.style.top = '0px'; - domElement.style.left = '0px'; - domElement.style.right = '0px'; - domElement.style.bottom = '0px'; - domElement.style.zIndex = 1000; - domElement.style.border = '0'; + try { + domElement.style.backgroundColor = 'rgba(0,0,0,0.2)'; + } catch (err) { + // Old IE browsers don't support rgba and we still want to show the upgrade message + // but we just make the background of the iframe completely transparent. + domElement.style.backgroundColor = 'transparent'; + domElement.setAttribute('allowTransparency', 'true'); + } - try { - domElement.style.backgroundColor = 'rgba(0,0,0,0.2)'; - } catch (err) { - // Old IE browsers don't support rgba and we still want to show the upgrade message - // but we just make the background of the iframe completely transparent. - domElement.style.backgroundColor = 'transparent'; - domElement.setAttribute('allowTransparency', 'true'); - } + domElement.scrolling = 'no'; + domElement.setAttribute('scrolling', 'no'); - domElement.scrolling = 'no'; - domElement.setAttribute('scrolling', 'no'); + // This is necessary for IE, as it will not inherit it's doctype from + // the parent frame. + var frameContent = '' + + '' + + '' + + ''; - // This is necessary for IE, as it will not inherit it's doctype from - // the parent frame. - var frameContent = '' + - '' + - '' + - ''; + var wrappedCallback = function() { + var doc = domElement.contentDocument || domElement.contentWindow.document; - var wrappedCallback = function() { - var doc = domElement.contentDocument || domElement.contentWindow.document; + if (OTHelpers.env.iframeNeedsLoad) { + doc.body.style.backgroundColor = 'transparent'; + doc.body.style.border = 'none'; - if (OTHelpers.browserVersion().iframeNeedsLoad) { - doc.body.style.backgroundColor = 'transparent'; - doc.body.style.border = 'none'; - - if (OTHelpers.browser() !== 'IE') { - // Skip this for IE as we use the bookmarklet workaround - // for THAT browser. - doc.open(); - doc.write(frameContent); - doc.close(); - } + if (OTHelpers.env.name !== 'IE') { + // Skip this for IE as we use the bookmarklet workaround + // for THAT browser. + doc.open(); + doc.write(frameContent); + doc.close(); } - - callback( - domElement.contentWindow, - doc - ); - }; - - document.body.appendChild(domElement); - - if(OTHelpers.browserVersion().iframeNeedsLoad) { - if (OTHelpers.browser() === 'IE') { - // This works around some issues with IE and document.write. - // Basically this works by slightly abusing the bookmarklet/scriptlet - // functionality that all browsers support. - domElement.contentWindow.contents = frameContent; - /*jshint scripturl:true*/ - domElement.src = 'javascript:window["contents"]'; - /*jshint scripturl:false*/ - } - - OTHelpers.on(domElement, 'load', wrappedCallback); - } else { - setTimeout(wrappedCallback); } - this.close = function() { - OTHelpers.removeElement(domElement); - this.trigger('closed'); - this.element = domElement = null; - return this; - }; - - this.element = domElement; - + callback( + domElement.contentWindow, + doc + ); }; -})(window, window.OTHelpers); + document.body.appendChild(domElement); + + if(OTHelpers.env.iframeNeedsLoad) { + if (OTHelpers.env.name === 'IE') { + // This works around some issues with IE and document.write. + // Basically this works by slightly abusing the bookmarklet/scriptlet + // functionality that all browsers support. + domElement.contentWindow.contents = frameContent; + /*jshint scripturl:true*/ + domElement.src = 'javascript:window["contents"]'; + /*jshint scripturl:false*/ + } + + OTHelpers.on(domElement, 'load', wrappedCallback); + } else { + setTimeout(wrappedCallback, 0); + } + + this.close = function() { + OTHelpers.removeElement(domElement); + this.trigger('closed'); + this.element = domElement = null; + return this; + }; + + this.element = domElement; + +}; /* * getComputedStyle from @@ -2371,7 +3016,7 @@ /*jshint strict: false, eqnull: true, browser:true, smarttabs:true*/ -(function(window, OTHelpers, undefined) { +(function() { /*jshint eqnull: true, browser: true */ @@ -2464,95 +3109,433 @@ } }; -})(window, window.OTHelpers); +})(); -// DOM Attribute helpers helpers +/*jshint browser:true, smarttabs:true */ -/*jshint browser:true, smarttabs:true*/ +// tb_require('../helpers.js') +// tb_require('./callbacks.js') +// tb_require('./dom.js') + +var observeStyleChanges = function observeStyleChanges (element, stylesToObserve, onChange) { + var oldStyles = {}; + + var getStyle = function getStyle(style) { + switch (style) { + case 'width': + return $(element).width(); + + case 'height': + return $(element).height(); + + default: + return $(element).css(style); + } + }; + + // get the inital values + $.forEach(stylesToObserve, function(style) { + oldStyles[style] = getStyle(style); + }); + + var observer = new MutationObserver(function(mutations) { + var changeSet = {}; + + $.forEach(mutations, function(mutation) { + if (mutation.attributeName !== 'style') return; + + var isHidden = $.isDisplayNone(element); + + $.forEach(stylesToObserve, function(style) { + if(isHidden && (style === 'width' || style === 'height')) return; + + var newValue = getStyle(style); + + if (newValue !== oldStyles[style]) { + changeSet[style] = [oldStyles[style], newValue]; + oldStyles[style] = newValue; + } + }); + }); + + if (!$.isEmpty(changeSet)) { + // Do this after so as to help avoid infinite loops of mutations. + $.callAsync(function() { + onChange.call(null, changeSet); + }); + } + }); + + observer.observe(element, { + attributes:true, + attributeFilter: ['style'], + childList:false, + characterData:false, + subtree:false + }); + + return observer; +}; + +var observeNodeOrChildNodeRemoval = function observeNodeOrChildNodeRemoval (element, onChange) { + var observer = new MutationObserver(function(mutations) { + var removedNodes = []; + + $.forEach(mutations, function(mutation) { + if (mutation.removedNodes.length) { + removedNodes = removedNodes.concat(prototypeSlice.call(mutation.removedNodes)); + } + }); + + if (removedNodes.length) { + // Do this after so as to help avoid infinite loops of mutations. + $.callAsync(function() { + onChange($(removedNodes)); + }); + } + }); + + observer.observe(element, { + attributes:false, + childList:true, + characterData:false, + subtree:true + }); + + return observer; +}; + +// Allows an +onChange+ callback to be triggered when specific style properties +// of +element+ are notified. The callback accepts a single parameter, which is +// a hash where the keys are the style property that changed and the values are +// an array containing the old and new values ([oldValue, newValue]). +// +// Width and Height changes while the element is display: none will not be +// fired until such time as the element becomes visible again. +// +// This function returns the MutationObserver itself. Once you no longer wish +// to observe the element you should call disconnect on the observer. +// +// Observing changes: +// // observe changings to the width and height of object +// dimensionsObserver = OTHelpers(object).observeStyleChanges(, +// ['width', 'height'], function(changeSet) { +// OT.debug("The new width and height are " + +// changeSet.width[1] + ',' + changeSet.height[1]); +// }); +// +// Cleaning up +// // stop observing changes +// dimensionsObserver.disconnect(); +// dimensionsObserver = null; +// +ElementCollection.prototype.observeStyleChanges = function(stylesToObserve, onChange) { + var observers = []; + + this.forEach(function(element) { + observers.push( + observeStyleChanges(element, stylesToObserve, onChange) + ); + }); + + return observers; +}; + +// trigger the +onChange+ callback whenever +// 1. +element+ is removed +// 2. or an immediate child of +element+ is removed. +// +// This function returns the MutationObserver itself. Once you no longer wish +// to observe the element you should call disconnect on the observer. +// +// Observing changes: +// // observe changings to the width and height of object +// nodeObserver = OTHelpers(object).observeNodeOrChildNodeRemoval(function(removedNodes) { +// OT.debug("Some child nodes were removed"); +// removedNodes.forEach(function(node) { +// OT.debug(node); +// }); +// }); +// +// Cleaning up +// // stop observing changes +// nodeObserver.disconnect(); +// nodeObserver = null; +// +ElementCollection.prototype.observeNodeOrChildNodeRemoval = function(onChange) { + var observers = []; + + this.forEach(function(element) { + observers.push( + observeNodeOrChildNodeRemoval(element, onChange) + ); + }); + + return observers; +}; + + +// @remove +OTHelpers.observeStyleChanges = function(element, stylesToObserve, onChange) { + return $(element).observeStyleChanges(stylesToObserve, onChange)[0]; +}; + +// @remove +OTHelpers.observeNodeOrChildNodeRemoval = function(element, onChange) { + return $(element).observeNodeOrChildNodeRemoval(onChange)[0]; +}; + +/*jshint browser:true, smarttabs:true */ // tb_require('../helpers.js') // tb_require('./dom.js') +// tb_require('./capabilities.js') -(function(window, OTHelpers, undefined) { +// Returns true if the client supports element.classList +OTHelpers.registerCapability('classList', function() { + return (typeof document !== 'undefined') && ('classList' in document.createElement('a')); +}); - OTHelpers.addClass = function(element, value) { - // Only bother targeting Element nodes, ignore Text Nodes, CDATA, etc - if (element.nodeType !== 1) { - return; + +function hasClass (element, className) { + if (!className) return false; + + if ($.hasCapabilities('classList')) { + return element.classList.contains(className); + } + + return element.className.indexOf(className) > -1; +} + +function toggleClasses (element, classNames) { + if (!classNames || classNames.length === 0) return; + + // Only bother targeting Element nodes, ignore Text Nodes, CDATA, etc + if (element.nodeType !== 1) { + return; + } + + var numClasses = classNames.length, + i = 0; + + if ($.hasCapabilities('classList')) { + for (; i 0) { return element.offsetWidth + 'px'; } - return OTHelpers.css(element, 'width'); + return $(element).css('width'); }, _height = function(element) { @@ -2560,58 +3543,58 @@ return element.offsetHeight + 'px'; } - return OTHelpers.css(element, 'height'); + return $(element).css('height'); }; - OTHelpers.width = function(element, newWidth) { + ElementCollection.prototype.width = function (newWidth) { if (newWidth) { - OTHelpers.css(element, 'width', newWidth); + this.css('width', newWidth); return this; } else { - if (OTHelpers.isDisplayNone(element)) { - // We can't get the width, probably since the element is hidden. - return OTHelpers.makeVisibleAndYield(element, function() { + if (this.isDisplayNone()) { + return this.makeVisibleAndYield(function(element) { return _width(element); - }); + })[0]; } else { - return _width(element); + return _width(this.get(0)); } } }; - OTHelpers.height = function(element, newHeight) { + ElementCollection.prototype.height = function (newHeight) { if (newHeight) { - OTHelpers.css(element, 'height', newHeight); + this.css('height', newHeight); return this; } else { - if (OTHelpers.isDisplayNone(element)) { + if (this.isDisplayNone()) { // We can't get the height, probably since the element is hidden. - return OTHelpers.makeVisibleAndYield(element, function() { + return this.makeVisibleAndYield(function(element) { return _height(element); - }); + })[0]; } else { - return _height(element); + return _height(this.get(0)); } } }; - // Centers +element+ within the window. You can pass through the width and height - // if you know it, if you don't they will be calculated for you. - OTHelpers.centerElement = function(element, width, height) { - if (!width) width = parseInt(OTHelpers.width(element), 10); - if (!height) height = parseInt(OTHelpers.height(element), 10); - - var marginLeft = -0.5 * width + 'px'; - var marginTop = -0.5 * height + 'px'; - OTHelpers.css(element, 'margin', marginTop + ' 0 0 ' + marginLeft); - OTHelpers.addClass(element, 'OT_centered'); + // @remove + OTHelpers.width = function(element, newWidth) { + var ret = $(element).width(newWidth); + return newWidth ? OTHelpers : ret; }; -})(window, window.OTHelpers); + // @remove + OTHelpers.height = function(element, newHeight) { + var ret = $(element).height(newHeight); + return newHeight ? OTHelpers : ret; + }; + +})(); + // CSS helpers helpers @@ -2621,12 +3604,12 @@ // tb_require('./dom.js') // tb_require('./getcomputedstyle.js') -(function(window, OTHelpers, undefined) { +(function() { var displayStateCache = {}, defaultDisplays = {}; - var defaultDisplayValueForElement = function(element) { + var defaultDisplayValueForElement = function (element) { if (defaultDisplays[element.ownerDocument] && defaultDisplays[element.ownerDocument][element.nodeName]) { return defaultDisplays[element.ownerDocument][element.nodeName]; @@ -2641,83 +3624,49 @@ element.ownerDocument.body.appendChild(testNode); defaultDisplay = defaultDisplays[element.ownerDocument][element.nodeName] = - OTHelpers.css(testNode, 'display'); + $(testNode).css('display'); - OTHelpers.removeElement(testNode); + $(testNode).remove(); testNode = null; return defaultDisplay; }; - var isHidden = function(element) { - var computedStyle = OTHelpers.getComputedStyle(element); + var isHidden = function (element) { + var computedStyle = $.getComputedStyle(element); return computedStyle.getPropertyValue('display') === 'none'; }; - OTHelpers.show = function(element) { - var display = element.style.display; + var setCssProperties = function (element, hash) { + var style = element.style; - if (display === '' || display === 'none') { - element.style.display = displayStateCache[element] || ''; - delete displayStateCache[element]; - } - - if (isHidden(element)) { - // It's still hidden so there's probably a stylesheet that declares this - // element as display:none; - displayStateCache[element] = 'none'; - - element.style.display = defaultDisplayValueForElement(element); - } - - return this; - }; - - OTHelpers.hide = function(element) { - if (element.style.display === 'none') return; - - displayStateCache[element] = element.style.display; - element.style.display = 'none'; - - return this; - }; - - OTHelpers.css = function(element, nameOrHash, value) { - if (typeof(nameOrHash) !== 'string') { - var style = element.style; - - for (var cssName in nameOrHash) { - if (nameOrHash.hasOwnProperty(cssName)) { - style[cssName] = nameOrHash[cssName]; - } + for (var cssName in hash) { + if (hash.hasOwnProperty(cssName)) { + style[cssName] = hash[cssName]; } - - return this; - - } else if (value !== undefined) { - element.style[nameOrHash] = value; - return this; - - } else { - // Normalise vendor prefixes from the form MozTranform to -moz-transform - // except for ms extensions, which are weird... - - var name = nameOrHash.replace( /([A-Z]|^ms)/g, '-$1' ).toLowerCase(), - computedStyle = OTHelpers.getComputedStyle(element), - currentValue = computedStyle.getPropertyValue(name); - - if (currentValue === '') { - currentValue = element.style[name]; - } - - return currentValue; } }; + var setCssProperty = function (element, name, value) { + element.style[name] = value; + }; -// Apply +styles+ to +element+ while executing +callback+, restoring the previous -// styles after the callback executes. - OTHelpers.applyCSS = function(element, styles, callback) { + var getCssProperty = function (element, unnormalisedName) { + // Normalise vendor prefixes from the form MozTranform to -moz-transform + // except for ms extensions, which are weird... + + var name = unnormalisedName.replace( /([A-Z]|^ms)/g, '-$1' ).toLowerCase(), + computedStyle = $.getComputedStyle(element), + currentValue = computedStyle.getPropertyValue(name); + + if (currentValue === '') { + currentValue = element.style[name]; + } + + return currentValue; + }; + + var applyCSS = function(element, styles, callback) { var oldStyles = {}, name, ret; @@ -2730,44 +3679,237 @@ // only want to pull values out of the style (domeElement.style) hash. oldStyles[name] = element.style[name]; - OTHelpers.css(element, name, styles[name]); + $(element).css(name, styles[name]); } } - ret = callback(); + ret = callback(element); // Restore the old styles for (name in styles) { if (styles.hasOwnProperty(name)) { - OTHelpers.css(element, name, oldStyles[name] || ''); + $(element).css(name, oldStyles[name] || ''); } } return ret; }; - // Make +element+ visible while executing +callback+. - OTHelpers.makeVisibleAndYield = function(element, callback) { - // find whether it's the element or an ancester that's display none and - // then apply to whichever it is - var targetElement = OTHelpers.findElementWithDisplayNone(element); - if (!targetElement) return; + ElementCollection.prototype.show = function() { + return this.forEach(function(element) { + var display = element.style.display; - return OTHelpers.applyCSS(targetElement, { - display: 'block', - visibility: 'hidden' - }, callback); + if (display === '' || display === 'none') { + element.style.display = displayStateCache[element] || ''; + delete displayStateCache[element]; + } + + if (isHidden(element)) { + // It's still hidden so there's probably a stylesheet that declares this + // element as display:none; + displayStateCache[element] = 'none'; + + element.style.display = defaultDisplayValueForElement(element); + } + }); }; -})(window, window.OTHelpers); + ElementCollection.prototype.hide = function() { + return this.forEach(function(element) { + if (element.style.display === 'none') return; -// AJAX helpers + displayStateCache[element] = element.style.display; + element.style.display = 'none'; + }); + }; + + ElementCollection.prototype.css = function(nameOrHash, value) { + if (this.length === 0) return; + + if (typeof(nameOrHash) !== 'string') { + + return this.forEach(function(element) { + setCssProperties(element, nameOrHash); + }); + + } else if (value !== undefined) { + + return this.forEach(function(element) { + setCssProperty(element, nameOrHash, value); + }); + + } else { + return getCssProperty(this.first, nameOrHash, value); + } + }; + + // Apply +styles+ to +element+ while executing +callback+, restoring the previous + // styles after the callback executes. + ElementCollection.prototype.applyCSS = function (styles, callback) { + var results = []; + + this.forEach(function(element) { + results.push(applyCSS(element, styles, callback)); + }); + + return results; + }; + + + // Make +element+ visible while executing +callback+. + ElementCollection.prototype.makeVisibleAndYield = function (callback) { + var hiddenVisually = { + display: 'block', + visibility: 'hidden' + }, + results = []; + + this.forEach(function(element) { + // find whether it's the element or an ancestor that's display none and + // then apply to whichever it is + var targetElement = $.findElementWithDisplayNone(element); + if (!targetElement) { + results.push(void 0); + } + else { + results.push( + applyCSS(targetElement, hiddenVisually, callback) + ); + } + }); + + return results; + }; + + + // @remove + OTHelpers.show = function(element) { + return $(element).show(); + }; + + // @remove + OTHelpers.hide = function(element) { + return $(element).hide(); + }; + + // @remove + OTHelpers.css = function(element, nameOrHash, value) { + return $(element).css(nameOrHash, value); + }; + + // @remove + OTHelpers.applyCSS = function(element, styles, callback) { + return $(element).applyCSS(styles, callback); + }; + + // @remove + OTHelpers.makeVisibleAndYield = function(element, callback) { + return $(element).makeVisibleAndYield(callback); + }; + +})(); + +// tb_require('../helpers.js') + +/**@licence + * Copyright (c) 2010 Caolan McMahon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + **/ + + +(function() { + + OTHelpers.setImmediate = (function() { + if (typeof process === 'undefined' || !(process.nextTick)) { + if (typeof setImmediate === 'function') { + return function (fn) { + // not a direct alias for IE10 compatibility + setImmediate(fn); + }; + } + return function (fn) { + setTimeout(fn, 0); + }; + } + if (typeof setImmediate !== 'undefined') { + return setImmediate; + } + return process.nextTick; + })(); + + OTHelpers.iterator = function(tasks) { + var makeCallback = function (index) { + var fn = function () { + if (tasks.length) { + tasks[index].apply(null, arguments); + } + return fn.next(); + }; + fn.next = function () { + return (index < tasks.length - 1) ? makeCallback(index + 1) : null; + }; + return fn; + }; + return makeCallback(0); + }; + + OTHelpers.waterfall = function(array, done) { + done = done || function () {}; + if (array.constructor !== Array) { + return done(new Error('First argument to waterfall must be an array of functions')); + } + + if (!array.length) { + return done(); + } + + var next = function(iterator) { + return function (err) { + if (err) { + done.apply(null, arguments); + done = function () {}; + } else { + var args = prototypeSlice.call(arguments, 1), + nextFn = iterator.next(); + if (nextFn) { + args.push(next(nextFn)); + } else { + args.push(done); + } + OTHelpers.setImmediate(function() { + iterator.apply(null, args); + }); + } + }; + }; + + next(OTHelpers.iterator(array))(); + }; + +})(); /*jshint browser:true, smarttabs:true*/ // tb_require('../helpers.js') -(function(window, OTHelpers, undefined) { +(function() { var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || @@ -2791,906 +3933,281 @@ } OTHelpers.requestAnimationFrame = requestAnimationFrame; -})(window, window.OTHelpers); +})(); +/*jshint browser:true, smarttabs:true*/ + +// tb_require('../helpers.js') + +(function() { + + // Singleton interval + var logQueue = [], + queueRunning = false; + + OTHelpers.Analytics = function(loggingUrl, debugFn) { + + var endPoint = loggingUrl + '/logging/ClientEvent', + endPointQos = loggingUrl + '/logging/ClientQos', + + reportedErrors = {}, + + send = function(data, isQos, callback) { + OTHelpers.post((isQos ? endPointQos : endPoint) + '?_=' + OTHelpers.uuid.v4(), { + body: data, + xdomainrequest: ($.env.name === 'IE' && $.env.version < 10), + headers: { + 'Content-Type': 'application/json' + } + }, callback); + }, + + throttledPost = function() { + // Throttle logs so that they only happen 1 at a time + if (!queueRunning && logQueue.length > 0) { + queueRunning = true; + var curr = logQueue[0]; + + // Remove the current item and send the next log + var processNextItem = function() { + logQueue.shift(); + queueRunning = false; + throttledPost(); + }; + + if (curr) { + send(curr.data, curr.isQos, function(err) { + if (err) { + var debugMsg = 'Failed to send ClientEvent, moving on to the next item.'; + if (debugFn) { + debugFn(debugMsg); + } else { + console.log(debugMsg); + } + // There was an error, move onto the next item + } else { + curr.onComplete(); + } + setTimeout(processNextItem, 50); + }); + } + } + }, + + post = function(data, onComplete, isQos) { + logQueue.push({ + data: data, + onComplete: onComplete, + isQos: isQos + }); + + throttledPost(); + }, + + shouldThrottleError = function(code, type, partnerId) { + if (!partnerId) return false; + + var errKey = [partnerId, type, code].join('_'), + //msgLimit = DynamicConfig.get('exceptionLogging', 'messageLimitPerPartner', partnerId); + msgLimit = 100; + if (msgLimit === null || msgLimit === undefined) return false; + return (reportedErrors[errKey] || 0) <= msgLimit; + }; + + // Log an error via ClientEvents. + // + // @param [String] code + // @param [String] type + // @param [String] message + // @param [Hash] details additional error details + // + // @param [Hash] options the options to log the client event with. + // @option options [String] action The name of the Event that we are logging. E.g. + // 'TokShowLoaded'. Required. + // @option options [String] variation Usually used for Split A/B testing, when you + // have multiple variations of the +_action+. + // @option options [String] payload The payload. Required. + // @option options [String] sessionId The active OpenTok session, if there is one + // @option options [String] connectionId The active OpenTok connectionId, if there is one + // @option options [String] partnerId + // @option options [String] guid ... + // @option options [String] streamId ... + // @option options [String] section ... + // @option options [String] clientVersion ... + // + // Reports will be throttled to X reports (see exceptionLogging.messageLimitPerPartner + // from the dynamic config for X) of each error type for each partner. Reports can be + // disabled/enabled globally or on a per partner basis (per partner settings + // take precedence) using exceptionLogging.enabled. + // + this.logError = function(code, type, message, details, options) { + if (!options) options = {}; + var partnerId = options.partnerId; + + if (shouldThrottleError(code, type, partnerId)) { + //OT.log('ClientEvents.error has throttled an error of type ' + type + '.' + + // code + ' for partner ' + (partnerId || 'No Partner Id')); + return; + } + + var errKey = [partnerId, type, code].join('_'), + payload = details ? details : null; + + reportedErrors[errKey] = typeof(reportedErrors[errKey]) !== 'undefined' ? + reportedErrors[errKey] + 1 : 1; + this.logEvent(OTHelpers.extend(options, { + action: type + '.' + code, + payload: payload + }), false); + }; + + // Log a client event to the analytics backend. + // + // @example Logs a client event called 'foo' + // this.logEvent({ + // action: 'foo', + // payload: 'bar', + // sessionId: sessionId, + // connectionId: connectionId + // }, false) + // + // @param [Hash] data the data to log the client event with. + // @param [Boolean] qos Whether this is a QoS event. + // + this.logEvent = function(data, qos, throttle) { + if (!qos) qos = false; + + if (throttle && !isNaN(throttle)) { + if (Math.random() > throttle) { + return; + } + } + + // remove properties that have null values: + for (var key in data) { + if (data.hasOwnProperty(key) && data[key] === null) { + delete data[key]; + } + } + + // TODO: catch error when stringifying an object that has a circular reference + data = JSON.stringify(data); + + var onComplete = function() { + // OT.log('logged: ' + '{action: ' + data['action'] + ', variation: ' + data['variation'] + // + ', payload: ' + data['payload'] + '}'); + }; + + post(data, onComplete, qos); + }; + + // Log a client QOS to the analytics backend. + // Log a client QOS to the analytics backend. + // @option options [String] action The name of the Event that we are logging. + // E.g. 'TokShowLoaded'. Required. + // @option options [String] variation Usually used for Split A/B testing, when + // you have multiple variations of the +_action+. + // @option options [String] payload The payload. Required. + // @option options [String] sessionId The active OpenTok session, if there is one + // @option options [String] connectionId The active OpenTok connectionId, if there is one + // @option options [String] partnerId + // @option options [String] guid ... + // @option options [String] streamId ... + // @option options [String] section ... + // @option options [String] clientVersion ... + // + this.logQOS = function(options) { + this.logEvent(options, true); + }; + }; + +})(); + // AJAX helpers /*jshint browser:true, smarttabs:true*/ // tb_require('../helpers.js') +// tb_require('./ajax/node.js') +// tb_require('./ajax/browser.js') -(function(window, OTHelpers, undefined) { - - function formatPostData(data) { //, contentType - // If it's a string, we assume it's properly encoded - if (typeof(data) === 'string') return data; - - var queryString = []; - - for (var key in data) { - queryString.push( - encodeURIComponent(key) + '=' + encodeURIComponent(data[key]) - ); - } - - return queryString.join('&').replace(/\+/g, '%20'); - } - - OTHelpers.getJSON = function(url, options, callback) { - options = options || {}; - - var done = function(error, event) { - if(error) { - callback(error, event && event.target && event.target.responseText); - } else { - var response; - - try { - response = JSON.parse(event.target.responseText); - } catch(e) { - // Badly formed JSON - callback(e, event && event.target && event.target.responseText); - return; - } - - callback(null, response, event); - } - }; - - if(options.xdomainrequest) { - OTHelpers.xdomainRequest(url, { method: 'GET' }, done); - } else { - var extendedHeaders = OTHelpers.extend({ - 'Accept': 'application/json' - }, options.headers || {}); - - OTHelpers.get(url, OTHelpers.extend(options || {}, { - headers: extendedHeaders - }), done); - } - - }; - - OTHelpers.xdomainRequest = function(url, options, callback) { - /*global XDomainRequest*/ - var xdr = new XDomainRequest(), - _options = options || {}, - _method = _options.method; - - if(!_method) { - callback(new Error('No HTTP method specified in options')); - return; - } - - _method = _method.toUpperCase(); - - if(!(_method === 'GET' || _method === 'POST')) { - callback(new Error('HTTP method can only be ')); - return; - } - - function done(err, event) { - xdr.onload = xdr.onerror = xdr.ontimeout = function() {}; - xdr = void 0; - callback(err, event); - } +OTHelpers.get = function(url, options, callback) { + var _options = OTHelpers.extend(options || {}, { + method: 'GET' + }); + OTHelpers.request(url, _options, callback); +}; - xdr.onload = function() { - done(null, { - target: { - responseText: xdr.responseText, - headers: { - 'content-type': xdr.contentType - } - } - }); - }; +OTHelpers.post = function(url, options, callback) { + var _options = OTHelpers.extend(options || {}, { + method: 'POST' + }); - xdr.onerror = function() { - done(new Error('XDomainRequest of ' + url + ' failed')); - }; - - xdr.ontimeout = function() { - done(new Error('XDomainRequest of ' + url + ' timed out')); - }; - - xdr.open(_method, url); - xdr.send(options.body && formatPostData(options.body)); - - }; - - OTHelpers.request = function(url, options, callback) { - var request = new XMLHttpRequest(), - _options = options || {}, - _method = _options.method; - - if(!_method) { - callback(new Error('No HTTP method specified in options')); - return; - } - - // Setup callbacks to correctly respond to success and error callbacks. This includes - // interpreting the responses HTTP status, which XmlHttpRequest seems to ignore - // by default. - if(callback) { - OTHelpers.on(request, 'load', function(event) { - var status = event.target.status; - - // We need to detect things that XMLHttpRequest considers a success, - // but we consider to be failures. - if ( status >= 200 && status < 300 || status === 304 ) { - callback(null, event); - } else { - callback(event); - } - }); - - OTHelpers.on(request, 'error', callback); - } - - request.open(options.method, url, true); - - if (!_options.headers) _options.headers = {}; - - for (var name in _options.headers) { - request.setRequestHeader(name, _options.headers[name]); - } - - request.send(options.body && formatPostData(options.body)); - }; - - OTHelpers.get = function(url, options, callback) { - var _options = OTHelpers.extend(options || {}, { - method: 'GET' - }); + if(_options.xdomainrequest) { + OTHelpers.xdomainRequest(url, _options, callback); + } else { OTHelpers.request(url, _options, callback); - }; + } +}; - OTHelpers.post = function(url, options, callback) { - var _options = OTHelpers.extend(options || {}, { - method: 'POST' - }); - - if(_options.xdomainrequest) { - OTHelpers.xdomainRequest(url, _options, callback); - } else { - OTHelpers.request(url, _options, callback); - } - }; })(window, window.OTHelpers); -!(function(window) { - /* global OTHelpers */ - if (!window.OT) window.OT = {}; - - // Bring OTHelpers in as OT.$ - OT.$ = OTHelpers.noConflict(); - - // Allow events to be bound on OT - OT.$.eventing(OT); - - // REMOVE THIS POST IE MERGE - - OT.$.defineGetters = function(self, getters, enumerable) { - var propsDefinition = {}; - - if (enumerable === void 0) enumerable = false; - - for (var key in getters) { - if(!getters.hasOwnProperty(key)) { - continue; - } - propsDefinition[key] = { - get: getters[key], - enumerable: enumerable - }; - } - - Object.defineProperties(self, propsDefinition); - }; - - // STOP REMOVING HERE - - // OT.$.Modal was OT.Modal before the great common-js-helpers move - OT.Modal = OT.$.Modal; - - // Add logging methods - OT.$.useLogHelpers(OT); - - var _debugHeaderLogged = false, - _setLogLevel = OT.setLogLevel; - - // On the first time log level is set to DEBUG (or higher) show version info. - OT.setLogLevel = function(level) { - // Set OT.$ to the same log level - OT.$.setLogLevel(level); - var retVal = _setLogLevel.call(OT, level); - if (OT.shouldLog(OT.DEBUG) && !_debugHeaderLogged) { - OT.debug('OpenTok JavaScript library ' + OT.properties.version); - OT.debug('Release notes: ' + OT.properties.websiteURL + - '/opentok/webrtc/docs/js/release-notes.html'); - OT.debug('Known issues: ' + OT.properties.websiteURL + - '/opentok/webrtc/docs/js/release-notes.html#knownIssues'); - _debugHeaderLogged = true; - } - OT.debug('OT.setLogLevel(' + retVal + ')'); - return retVal; - }; - - var debugTrue = OT.properties.debug === 'true' || OT.properties.debug === true; - OT.setLogLevel(debugTrue ? OT.DEBUG : OT.ERROR); - - OT.$.userAgent = function() { - var userAgent = navigator.userAgent; - if (TBPlugin.isInstalled()) userAgent += '; TBPlugin ' + TBPlugin.version(); - return userAgent; - }; - - /** - * Sets the API log level. - *

- * Calling OT.setLogLevel() sets the log level for runtime log messages that - * are the OpenTok library generates. The default value for the log level is OT.ERROR. - *

- *

- * The OpenTok JavaScript library displays log messages in the debugger console (such as - * Firebug), if one exists. - *

- *

- * The following example logs the session ID to the console, by calling OT.log(). - * The code also logs an error message when it attempts to publish a stream before the Session - * object dispatches a sessionConnected event. - *

- *
-  * OT.setLogLevel(OT.LOG);
-  * session = OT.initSession(sessionId);
-  * OT.log(sessionId);
-  * publisher = OT.initPublisher("publishContainer");
-  * session.publish(publisher);
-  * 
- * - * @param {Number} logLevel The degree of logging desired by the developer: - * - *

- *

    - *
  • - * OT.NONE — API logging is disabled. - *
  • - *
  • - * OT.ERROR — Logging of errors only. - *
  • - *
  • - * OT.WARN — Logging of warnings and errors. - *
  • - *
  • - * OT.INFO — Logging of other useful information, in addition to - * warnings and errors. - *
  • - *
  • - * OT.LOG — Logging of OT.log() messages, in addition - * to OpenTok info, warning, - * and error messages. - *
  • - *
  • - * OT.DEBUG — Fine-grained logging of all API actions, as well as - * OT.log() messages. - *
  • - *
- *

- * - * @name OT.setLogLevel - * @memberof OT - * @function - * @see OT.log() - */ - - /** - * Sends a string to the the debugger console (such as Firebug), if one exists. - * However, the function only logs to the console if you have set the log level - * to OT.LOG or OT.DEBUG, - * by calling OT.setLogLevel(OT.LOG) or OT.setLogLevel(OT.DEBUG). - * - * @param {String} message The string to log. - * - * @name OT.log - * @memberof OT - * @function - * @see OT.setLogLevel() - */ - -})(window); -!(function() { - - var adjustModal = function(callback) { - return function setFullHeightDocument(window, document) { - // required in IE8 - document.querySelector('html').style.height = document.body.style.height = '100%'; - callback(window, document); - }; - }; - - var addCss = function(document, url, callback) { - var head = document.head || document.getElementsByTagName('head')[0]; - var cssTag = OT.$.createElement('link', { - type: 'text/css', - media: 'screen', - rel: 'stylesheet', - href: url - }); - head.appendChild(cssTag); - OT.$.on(cssTag, 'error', function(error) { - OT.error('Could not load CSS for dialog', url, error && error.message || error); - }); - OT.$.on(cssTag, 'load', callback); - }; - - var addDialogCSS = function(document, urls, callback) { - var allURLs = [ - '//fonts.googleapis.com/css?family=Didact+Gothic', - OT.properties.cssURL - ].concat(urls); - var remainingStylesheets = allURLs.length; - OT.$.forEach(allURLs, function(stylesheetUrl) { - addCss(document, stylesheetUrl, function() { - if(--remainingStylesheets <= 0) { - callback(); - } - }); - }); - - }; - - var templateElement = function(classes, children, tagName) { - var el = OT.$.createElement(tagName || 'div', { 'class': classes }, children, this); - el.on = OT.$.bind(OT.$.on, OT.$, el); - el.off = OT.$.bind(OT.$.off, OT.$, el); - return el; - }; - - var checkBoxElement = function (classes, nameAndId, onChange) { - var checkbox = templateElement.call(this, '', null, 'input').on('change', onChange); - - if (OT.$.browser() === 'IE' && OT.$.browserVersion().version <= 8) { - // Fix for IE8 not triggering the change event - checkbox.on('click', function() { - checkbox.blur(); - checkbox.focus(); - }); - } - - checkbox.setAttribute('name', nameAndId); - checkbox.setAttribute('id', nameAndId); - checkbox.setAttribute('type', 'checkbox'); - - return checkbox; - }; - - var linkElement = function(children, href, classes) { - var link = templateElement.call(this, classes || '', children, 'a'); - link.setAttribute('href', href); - return link; - }; - - OT.Dialogs = {}; - - OT.Dialogs.Plugin = {}; - - OT.Dialogs.Plugin.promptToInstall = function() { - var modal = new OT.$.Modal(adjustModal(function(window, document) { - - var el = OT.$.bind(templateElement, document), - btn = function(children, size) { - var classes = 'OT_dialog-button ' + - (size ? 'OT_dialog-button-' + size : 'OT_dialog-button-large'), - b = el(classes, children); - - b.enable = function() { - OT.$.removeClass(this, 'OT_dialog-button-disabled'); - return this; - }; - - b.disable = function() { - OT.$.addClass(this, 'OT_dialog-button-disabled'); - return this; - }; - - return b; - }, - downloadButton = btn('Download plugin'), - cancelButton = btn('cancel', 'small'), - refreshButton = btn('Refresh browser'), - acceptEULA, - checkbox, - close, - root; - - OT.$.addClass(cancelButton, 'OT_dialog-no-natural-margin OT_dialog-button-block'); - OT.$.addClass(refreshButton, 'OT_dialog-no-natural-margin'); - - function onDownload() { - modal.trigger('download'); - setTimeout(function() { - root.querySelector('.OT_dialog-messages-main').innerHTML = - 'Plugin installation successful'; - var sections = root.querySelectorAll('.OT_dialog-section'); - OT.$.addClass(sections[0], 'OT_dialog-hidden'); - OT.$.removeClass(sections[1], 'OT_dialog-hidden'); - }, 3000); - } - - function onRefresh() { - modal.trigger('refresh'); - } - - function onToggleEULA() { - if (checkbox.checked) { - enableButtons(); - } - else { - disableButtons(); - } - } - - function enableButtons() { - downloadButton.enable(); - downloadButton.on('click', onDownload); - - refreshButton.enable(); - refreshButton.on('click', onRefresh); - } - - function disableButtons() { - downloadButton.disable(); - downloadButton.off('click', onDownload); - - refreshButton.disable(); - refreshButton.off('click', onRefresh); - } - - downloadButton.disable(); - refreshButton.disable(); - - cancelButton.on('click', function() { - modal.trigger('cancelButtonClicked'); - modal.close(); - }); - - close = el('OT_closeButton', '×') - .on('click', function() { - modal.trigger('closeButtonClicked'); - modal.close(); - }); - - acceptEULA = linkElement.call(document, - 'end-user license agreement', - 'http://tokbox.com/support/ie-eula'); - - checkbox = checkBoxElement.call(document, null, 'acceptEULA', onToggleEULA); - - root = el('OT_dialog-centering', [ - el('OT_dialog-centering-child', [ - el('OT_root OT_dialog OT_dialog-plugin-prompt', [ - close, - el('OT_dialog-messages', [ - el('OT_dialog-messages-main', 'This app requires real-time communication') - ]), - el('OT_dialog-section', [ - el('OT_dialog-single-button-with-title', [ - el('OT_dialog-button-title', [ - checkbox, - (function() { - var x = el('', 'accept', 'label'); - x.setAttribute('for', checkbox.id); - x.style.margin = '0 5px'; - return x; - })(), - acceptEULA - ]), - el('OT_dialog-actions-card', [ - downloadButton, - cancelButton - ]) - ]) - ]), - el('OT_dialog-section OT_dialog-hidden', [ - el('OT_dialog-button-title', [ - 'You can now enjoy webRTC enabled video via Internet Explorer.' - ]), - refreshButton - ]) - ]) - ]) - ]); - - addDialogCSS(document, [], function() { - document.body.appendChild(root); - }); - - })); - return modal; - }; - - OT.Dialogs.Plugin.promptToReinstall = function() { - var modal = new OT.$.Modal(adjustModal(function(window, document) { - - var el = OT.$.bind(templateElement, document), - close, - okayButton, - root; - - close = el('OT_closeButton', '×'); - okayButton = - el('OT_dialog-button OT_dialog-button-large OT_dialog-no-natural-margin', 'Okay'); - - OT.$.on(okayButton, 'click', function() { - modal.trigger('okay'); - }); - - OT.$.on(close, 'click', function() { - modal.trigger('closeButtonClicked'); - modal.close(); - }); - - root = el('OT_dialog-centering', [ - el('OT_dialog-centering-child', [ - el('OT_ROOT OT_dialog OT_dialog-plugin-reinstall', [ - close, - el('OT_dialog-messages', [ - el('OT_dialog-messages-main', 'Reinstall Opentok Plugin'), - el('OT_dialog-messages-minor', 'Uh oh! Try reinstalling the OpenTok plugin ' + - 'again to enable real-time video communication for Internet Explorer.') - ]), - el('OT_dialog-section', [ - el('OT_dialog-single-button', okayButton) - ]) - ]) - ]) - ]); - - addDialogCSS(document, [], function() { - document.body.appendChild(root); - }); - - })); - - return modal; - }; - - OT.Dialogs.Plugin.updateInProgress = function() { - - var progressBar, - progressText, - progressValue = 0; - - var modal = new OT.$.Modal(adjustModal(function(window, document) { - - var el = OT.$.bind(templateElement, document), - root; - - progressText = el('OT_dialog-plugin-upgrade-percentage', '0%', 'strong'); - - progressBar = el('OT_dialog-progress-bar-fill'); - - root = el('OT_dialog-centering', [ - el('OT_dialog-centering-child', [ - el('OT_ROOT OT_dialog OT_dialog-plugin-upgrading', [ - el('OT_dialog-messages', [ - el('OT_dialog-messages-main', [ - 'One moment please... ', - progressText - ]), - el('OT_dialog-progress-bar', progressBar), - el('OT_dialog-messages-minor OT_dialog-no-natural-margin', - 'Please wait while the OpenTok plugin is updated') - ]) - ]) - ]) - ]); - - addDialogCSS(document, [], function() { - document.body.appendChild(root); - if(progressValue != null) { - modal.setUpdateProgress(progressValue); - } - }); - })); - - modal.setUpdateProgress = function(newProgress) { - if(progressBar && progressText) { - if(newProgress > 99) { - OT.$.css(progressBar, 'width', ''); - progressText.innerHTML = '100%'; - } else if(newProgress < 1) { - OT.$.css(progressBar, 'width', '0%'); - progressText.innerHTML = '0%'; - } else { - OT.$.css(progressBar, 'width', newProgress + '%'); - progressText.innerHTML = newProgress + '%'; - } - } else { - progressValue = newProgress; - } - }; - - return modal; - }; - - OT.Dialogs.Plugin.updateComplete = function(error) { - var modal = new OT.$.Modal(adjustModal(function(window, document) { - var el = OT.$.bind(templateElement, document), - reloadButton, - root; - - reloadButton = - el('OT_dialog-button OT_dialog-button-large OT_dialog-no-natural-margin', 'Reload') - .on('click', function() { - modal.trigger('reload'); - }); - - var msgs; - - if(error) { - msgs = ['Update Failed.', error + '' || 'NO ERROR']; - } else { - msgs = ['Update Complete.', - 'The OpenTok plugin has been succesfully updated. ' + - 'Please reload your browser.']; - } - - root = el('OT_dialog-centering', [ - el('OT_dialog-centering-child', [ - el('OT_root OT_dialog OT_dialog-plugin-upgraded', [ - el('OT_dialog-messages', [ - el('OT_dialog-messages-main', msgs[0]), - el('OT_dialog-messages-minor', msgs[1]) - ]), - el('OT_dialog-single-button', reloadButton) - ]) - ]) - ]); - - addDialogCSS(document, [], function() { - document.body.appendChild(root); - }); - - })); - - return modal; - - }; - - -})(); -!(function(window) { - - // IMPORTANT This file should be included straight after helpers.js - if (!window.OT) window.OT = {}; - - if (!OT.properties) { - throw new Error('OT.properties does not exist, please ensure that you include a valid ' + - 'properties file.'); - } - - OT.useSSL = function () { - return OT.properties.supportSSL && (window.location.protocol.indexOf('https') >= 0 || - window.location.protocol.indexOf('chrome-extension') >= 0); - }; - - // Consumes and overwrites OT.properties. Makes it better and stronger! - OT.properties = function(properties) { - var props = OT.$.clone(properties); - - props.debug = properties.debug === 'true' || properties.debug === true; - props.supportSSL = properties.supportSSL === 'true' || properties.supportSSL === true; - - if (window.OTProperties) { - // Allow window.OTProperties to override cdnURL, configURL, assetURL and cssURL - if (window.OTProperties.cdnURL) props.cdnURL = window.OTProperties.cdnURL; - if (window.OTProperties.cdnURLSSL) props.cdnURLSSL = window.OTProperties.cdnURLSSL; - if (window.OTProperties.configURL) props.configURL = window.OTProperties.configURL; - if (window.OTProperties.assetURL) props.assetURL = window.OTProperties.assetURL; - if (window.OTProperties.cssURL) props.cssURL = window.OTProperties.cssURL; - } - - if (!props.assetURL) { - if (OT.useSSL()) { - props.assetURL = props.cdnURLSSL + '/webrtc/' + props.version; - } else { - props.assetURL = props.cdnURL + '/webrtc/' + props.version; - } - } - - var isIE89 = OT.$.browser() === 'IE' && OT.$.browserVersion().version <= 9; - if (!(isIE89 && window.location.protocol.indexOf('https') < 0)) { - props.apiURL = props.apiURLSSL; - props.loggingURL = props.loggingURLSSL; - } - - if (!props.configURL) props.configURL = props.assetURL + '/js/dynamic_config.min.js'; - if (!props.cssURL) props.cssURL = props.assetURL + '/css/ot.min.css'; - - return props; - }(OT.properties); -})(window); -!(function() { - -//-------------------------------------- -// JS Dynamic Config -//-------------------------------------- - - - OT.Config = (function() { - var _loaded = false, - _global = {}, - _partners = {}, - _script, - _head = document.head || document.getElementsByTagName('head')[0], - _loadTimer, - - _clearTimeout = function() { - if (_loadTimer) { - clearTimeout(_loadTimer); - _loadTimer = null; - } - }, - - _cleanup = function() { - _clearTimeout(); - - if (_script) { - _script.onload = _script.onreadystatechange = null; - - if ( _head && _script.parentNode ) { - _head.removeChild( _script ); - } - - _script = undefined; - } - }, - - _onLoad = function() { - // Only IE and Opera actually support readyState on Script elements. - if (_script.readyState && !/loaded|complete/.test( _script.readyState )) { - // Yeah, we're not ready yet... - return; - } - - _clearTimeout(); - - if (!_loaded) { - // Our config script is loaded but there is not config (as - // replaceWith wasn't called). Something went wrong. Possibly - // the file we loaded wasn't actually a valid config file. - _this._onLoadTimeout(); - } - }, - - _getModule = function(moduleName, apiKey) { - if (apiKey && _partners[apiKey] && _partners[apiKey][moduleName]) { - return _partners[apiKey][moduleName]; - } - - return _global[moduleName]; - }, - - _this; - - _this = { - // In ms - loadTimeout: 4000, - - load: function(configUrl) { - if (!configUrl) throw new Error('You must pass a valid configUrl to Config.load'); - - _loaded = false; - - setTimeout(function() { - _script = document.createElement( 'script' ); - _script.async = 'async'; - _script.src = configUrl; - _script.onload = _script.onreadystatechange = OT.$.bind(_onLoad, this); - _head.appendChild(_script); - },1); - - _loadTimer = setTimeout(function() { - _this._onLoadTimeout(); - }, this.loadTimeout); - }, - - _onLoadTimeout: function() { - _cleanup(); - - OT.warn('TB DynamicConfig failed to load in ' + _this.loadTimeout + ' ms'); - this.trigger('dynamicConfigLoadFailed'); - }, - - isLoaded: function() { - return _loaded; - }, - - reset: function() { - _cleanup(); - _loaded = false; - _global = {}; - _partners = {}; - }, - - // This is public so that the dynamic config file can load itself. - // Using it for other purposes is discouraged, but not forbidden. - replaceWith: function(config) { - _cleanup(); - - if (!config) config = {}; - - _global = config.global || {}; - _partners = config.partners || {}; - - if (!_loaded) _loaded = true; - this.trigger('dynamicConfigChanged'); - }, - - // @example Get the value that indicates whether exceptionLogging is enabled - // OT.Config.get('exceptionLogging', 'enabled'); - // - // @example Get a key for a specific partner, fallback to the default if there is - // no key for that partner - // OT.Config.get('exceptionLogging', 'enabled', 'apiKey'); - // - get: function(moduleName, key, apiKey) { - var module = _getModule(moduleName, apiKey); - return module ? module[key] : null; - } - }; - - OT.$.eventing(_this); - - return _this; - })(); - -})(window); /** - * @license TB Plugin 0.4.0.8 59e99bc HEAD + * @license TB Plugin 0.4.0.9 88af499 2014Q4-2.2 * http://www.tokbox.com/ * * Copyright (c) 2015 TokBox, Inc. * - * Date: January 26 03:18:16 2015 + * Date: January 08 08:54:38 2015 * */ /* jshint globalstrict: true, strict: false, undef: true, unused: false, trailing: true, browser: true, smarttabs:true */ -/* global scope:true, OT:true */ -/* exported TBPlugin */ +/* global scope:true, OT:true, OTHelpers:true */ +/* exported OTPlugin */ /* jshint ignore:start */ (function(scope) { /* jshint ignore:end */ // If we've already be setup, bail -if (scope.TBPlugin !== void 0) return; +if (scope.OTPlugin !== void 0) return; + // TB must exist first, otherwise we can't do anything -if (scope.OT === void 0) return; +// if (scope.OT === void 0) return; // Establish the environment that we're running in -var env = OT.$.browserVersion(), - isSupported = env.browser === 'IE' && env.version >= 8, - pluginReady = false; +// Note: we don't currently support 64bit IE +var isSupported = (OTHelpers.env.name === 'IE' && OTHelpers.env.version >= 8 && + OTHelpers.env.userAgent.indexOf('x64') === -1), + pluginIsReady = false; -var TBPlugin = { + +var OTPlugin = { isSupported: function () { return isSupported; }, - isReady: function() { return pluginReady; } + isReady: function() { return pluginIsReady; }, + meta: { + mimeType: 'application/x-opentokie,version=0.4.0.9', + activeXName: 'TokBox.OpenTokIE.0.4.0.9', + version: '0.4.0.9' + } }; -scope.TBPlugin = TBPlugin; -// We only support IE, version 10 or above right now -if (!TBPlugin.isSupported()) { - TBPlugin.isInstalled = function isInstalled () { return false; }; +// Add logging methods +OTHelpers.useLogHelpers(OTPlugin); + +scope.OTPlugin = OTPlugin; + +// If this client isn't supported we still make sure that OTPlugin is defined +// and the basic API (isSupported() and isInstalled()) is created. +if (!OTPlugin.isSupported()) { + OTPlugin.isInstalled = function isInstalled () { return false; }; return; } @@ -3793,36 +4310,51 @@ var shim = function shim () { // tb_require('./header.js') // tb_require('./shims.js') -/* global OT:true */ -/* exported PluginRumorSocket */ +/* exported RumorSocket */ -var PluginRumorSocket = function(plugin, server) { +var RumorSocket = function(plugin, server) { var connected = false, rumorID; + var _onOpen, + _onClose; + + try { rumorID = plugin._.RumorInit(server, ''); } catch(e) { - OT.error('Error creating the Rumor Socket: ', e.message); + OTPlugin.error('Error creating the Rumor Socket: ', e.message); } if(!rumorID) { - throw new Error('Could not initialise plugin rumor connection'); + throw new Error('Could not initialise OTPlugin rumor connection'); } - var socket = { + plugin._.SetOnRumorOpen(rumorID, function() { + if (_onOpen && OTHelpers.isFunction(_onOpen)) { + _onOpen.call(null); + } + }); + + plugin._.SetOnRumorClose(rumorID, function(code) { + _onClose(code); + + // We're done. Clean up ourselves + plugin.removeRef(this); + }); + + var api = { open: function() { connected = true; plugin._.RumorOpen(rumorID); }, close: function(code, reason) { - if (!connected) return; - connected = false; - - plugin._.RumorClose(rumorID, code, reason); - plugin.removeRef(this); + if (connected) { + connected = false; + plugin._.RumorClose(rumorID, code, reason); + } }, destroy: function() { @@ -3835,11 +4367,11 @@ var PluginRumorSocket = function(plugin, server) { }, onOpen: function(callback) { - plugin._.SetOnRumorOpen(rumorID, callback); + _onOpen = callback; }, onClose: function(callback) { - plugin._.SetOnRumorClose(rumorID, callback); + _onClose = callback; }, onError: function(callback) { @@ -3851,9 +4383,8 @@ var PluginRumorSocket = function(plugin, server) { } }; - plugin.addRef(socket); - return socket; - + plugin.addRef(api); + return api; }; // tb_require('./header.js') @@ -3861,8 +4392,7 @@ var PluginRumorSocket = function(plugin, server) { /* jshint globalstrict: true, strict: false, undef: true, unused: true, trailing: true, browser: true, smarttabs:true */ -/* global OT:true, TBPlugin:true, pluginInfo:true, debug:true, scope:true, - _document:true */ +/* global OT:true, scope:true, injectObject:true */ /* exported createMediaCaptureController:true, createPeerController:true, injectObject:true, plugins:true, mediaCaptureObject:true, removeAllObjects:true, curryCallAsync:true */ @@ -3875,14 +4405,10 @@ var curryCallAsync = function curryCallAsync (fn) { return function() { var args = Array.prototype.slice.call(arguments); args.unshift(fn); - OT.$.callAsync.apply(OT.$, args); + OTHelpers.callAsync.apply(OTHelpers, args); }; }; -var generatePluginUuid = function generatePluginUuid () { - return OT.$.uuid().replace(/\-+/g, ''); -}; - var clearObjectLoadTimeout = function clearObjectLoadTimeout (callbackId) { if (!callbackId) return; @@ -3902,7 +4428,7 @@ var clearObjectLoadTimeout = function clearObjectLoadTimeout (callbackId) { }; var removeObjectFromDom = function removeObjectFromDom (object) { - clearObjectLoadTimeout(object.getAttribute('tb_callbackId')); + clearObjectLoadTimeout(object.getAttribute('tbCallbackId')); if (mediaCaptureObject && mediaCaptureObject.id === object.id) { mediaCaptureObject = null; @@ -3911,7 +4437,7 @@ var removeObjectFromDom = function removeObjectFromDom (object) { delete plugins[object.id]; } - object.parentNode.removeChild(object); + OTHelpers.removeElement(object); }; // @todo bind destroy to unload, may need to coordinate with TB @@ -3927,7 +4453,7 @@ var removeAllObjects = function removeAllObjects () { }; // Reference counted wrapper for a plugin object -var PluginObject = function PluginObject (plugin) { +var PluginProxy = function PluginProxy (plugin) { var _plugin = plugin, _liveObjects = []; @@ -3961,7 +4487,7 @@ var PluginObject = function PluginObject (plugin) { var eventHandlers = {}; - var onCustomEvent = OT.$.bind(curryCallAsync(function onCustomEvent() { + var onCustomEvent = OTHelpers.bind(curryCallAsync(function onCustomEvent() { var args = Array.prototype.slice.call(arguments), name = args.shift(); @@ -3969,7 +4495,7 @@ var PluginObject = function PluginObject (plugin) { return; } - OT.$.forEach(eventHandlers[name], function(handler) { + OTHelpers.forEach(eventHandlers[name], function(handler) { handler[0].apply(handler[1], args); }); }), this); @@ -3990,7 +4516,7 @@ var PluginObject = function PluginObject (plugin) { return; } - OT.$.filter(eventHandlers[name], function(listener) { + OTHelpers.filter(eventHandlers[name], function(listener) { return listener[0] === callback && listener[1] === context; }); @@ -4017,7 +4543,7 @@ var PluginObject = function PluginObject (plugin) { // Only the main plugin has an initialise method if (_plugin.initialise) { - this.on('ready', OT.$.bind(curryCallAsync(readyCallback), this)); + this.on('ready', OTHelpers.bind(curryCallAsync(readyCallback), this)); _plugin.initialise(); } else { @@ -4030,7 +4556,7 @@ var PluginObject = function PluginObject (plugin) { _liveObjects.shift().destroy(); } - removeObjectFromDom(_plugin); + if (_plugin) removeObjectFromDom(_plugin); _plugin = null; }; @@ -4040,6 +4566,8 @@ var PluginObject = function PluginObject (plugin) { // FIX ME renderingStarted currently doesn't first // this.once('renderingStarted', completion); var verifyStream = function() { + if (!_plugin) return; + if (_plugin.videoWidth > 0) { // This fires a little too soon. setTimeout(completion, 200); @@ -4061,136 +4589,12 @@ var PluginObject = function PluginObject (plugin) { }; }; -// Stops and cleans up after the plugin object load timeout. -var injectObject = function injectObject (mimeType, isVisible, params, completion) { - var callbackId = 'TBPlugin_loaded_' + generatePluginUuid(); - params.onload = callbackId; - params.userAgent = window.navigator.userAgent.toLowerCase(); - - scope[callbackId] = function() { - clearObjectLoadTimeout(callbackId); - - o.setAttribute('id', 'tb_plugin_' + o.uuid); - o.removeAttribute('tb_callbackId'); - - pluginRefCounted.uuid = o.uuid; - pluginRefCounted.id = o.id; - - pluginRefCounted.onReady(function(err) { - if (err) { - OT.error('Error while starting up plugin ' + o.uuid + ': ' + err); - return; - } - - debug('Plugin ' + o.id + ' is loaded'); - - if (completion && OT.$.isFunction(completion)) { - completion.call(TBPlugin, null, pluginRefCounted); - } - }); - }; - - var tmpContainer = document.createElement('div'), - objBits = [], - extraAttributes = ['width="0" height="0"'], - pluginRefCounted, - o; - - if (isVisible !== true) { - extraAttributes.push('visibility="hidden"'); - } - - objBits.push(''); - - for (var name in params) { - if (params.hasOwnProperty(name)) { - objBits.push(''); - } - } - - objBits.push(''); - tmpContainer.innerHTML = objBits.join(''); - - _document.body.appendChild(tmpContainer); - - function firstElementChild(element) { - if(element.firstElementChild) { - return element.firstElementChild; - } - for(var i = 0, len = element.childNodes.length; i < len; ++i) { - if(element.childNodes[i].nodeType === 1) { - return element.childNodes[i]; - } - } - return null; - } - - o = firstElementChild(tmpContainer); - o.setAttribute('tb_callbackId', callbackId); - - pluginRefCounted = new PluginObject(o); - - _document.body.appendChild(o); - _document.body.removeChild(tmpContainer); - - objectTimeouts[callbackId] = setTimeout(function() { - clearObjectLoadTimeout(callbackId); - - completion.call(TBPlugin, 'The object with the mimeType of ' + - mimeType + ' timed out while loading.'); - - _document.body.removeChild(o); - }, 3000); - - return pluginRefCounted; -}; - - -// Creates the Media Capture controller. This exposes selectSources and is -// used in the private API. -// -// Only one Media Capture controller can exist at once, calling this method -// more than once will raise an exception. -// -var createMediaCaptureController = function createMediaCaptureController (completion) { - if (mediaCaptureObject) { - throw new Error('TBPlugin.createMediaCaptureController called multiple times!'); - } - - mediaCaptureObject = injectObject(pluginInfo.mimeType, false, {windowless: false}, completion); - - mediaCaptureObject.selectSources = function() { - return this._.selectSources.apply(this._, arguments); - }; - - return mediaCaptureObject; -}; - -// Create an instance of the publisher/subscriber/peerconnection object. -// Many of these can exist at once, but the +id+ of each must be unique -// within a single instance of scope (window or window-like thing). -// -var createPeerController = function createPeerController (completion) { - var o = injectObject(pluginInfo.mimeType, true, {windowless: true}, function(err, plugin) { - if (err) { - completion.call(TBPlugin, err); - return; - } - - plugins[plugin.id] = plugin; - completion.call(TBPlugin, null, plugin); - }); - - return o; -}; - // tb_require('./header.js') // tb_require('./shims.js') -// tb_require('./plugin_object.js') +// tb_require('./proxy.js') /* jshint globalstrict: true, strict: false, undef: true, unused: true, trailing: true, browser: true, smarttabs:true */ -/* global OT:true, debug:true */ /* exported VideoContainer */ var VideoContainer = function VideoContainer (plugin, stream) { @@ -4201,38 +4605,38 @@ var VideoContainer = function VideoContainer (plugin, stream) { this.appendTo = function (parentDomElement) { if (parentDomElement && plugin._.parentNode !== parentDomElement) { - debug('VideoContainer appendTo', parentDomElement); + OTPlugin.debug('VideoContainer appendTo', parentDomElement); parentDomElement.appendChild(plugin._); this.parentElement = parentDomElement; } }; this.show = function (completion) { - debug('VideoContainer show'); + OTPlugin.debug('VideoContainer show'); plugin._.removeAttribute('width'); plugin._.removeAttribute('height'); plugin.setStream(stream, completion); - OT.$.show(plugin._); + OTHelpers.show(plugin._); }; this.setWidth = function (width) { - debug('VideoContainer setWidth to ' + width); + OTPlugin.debug('VideoContainer setWidth to ' + width); plugin._.setAttribute('width', width); }; this.setHeight = function (height) { - debug('VideoContainer setHeight to ' + height); + OTPlugin.debug('VideoContainer setHeight to ' + height); plugin._.setAttribute('height', height); }; this.setVolume = function (value) { // TODO - debug('VideoContainer setVolume not implemented: called with ' + value); + OTPlugin.debug('VideoContainer setVolume not implemented: called with ' + value); }; this.getVolume = function () { // TODO - debug('VideoContainer getVolume not implemented'); + OTPlugin.debug('VideoContainer getVolume not implemented'); return 0.5; }; @@ -4256,7 +4660,7 @@ var VideoContainer = function VideoContainer (plugin, stream) { // tb_require('./header.js') // tb_require('./shims.js') -// tb_require('./plugin_object.js') +// tb_require('./proxy.js') /* jshint globalstrict: true, strict: false, undef: true, unused: true, trailing: true, browser: true, smarttabs:true */ @@ -4272,238 +4676,23 @@ var RTCStatsReport = function (reports) { // tb_require('./header.js') // tb_require('./shims.js') -// tb_require('./plugin_object.js') -// tb_require('./stats.js') - -/* jshint globalstrict: true, strict: false, undef: true, unused: true, - trailing: true, browser: true, smarttabs:true */ -/* global OT:true, TBPlugin:true, MediaStream:true, RTCStatsReport:true */ -/* exported PeerConnection */ - -// Our RTCPeerConnection shim, it should look like a normal PeerConection -// from the outside, but it actually delegates to our plugin. -// -var PeerConnection = function PeerConnection (iceServers, options, plugin) { - var id = OT.$.uuid(), - hasLocalDescription = false, - hasRemoteDescription = false, - candidates = []; - - plugin.addRef(this); - - var onAddIceCandidate = function onAddIceCandidate () {/* success */}, - - onAddIceCandidateFailed = function onAddIceCandidateFailed (err) { - OT.error('Failed to process candidate'); - OT.error(err); - }, - - processPendingCandidates = function processPendingCandidates () { - for (var i=0; i'); + + for (var name in params) { + if (params.hasOwnProperty(name)) { + objBits.push(''); + } + } + + objBits.push(''); + + scope.document.body.insertAdjacentHTML('beforeend', objBits.join('')); + plugin = new PluginProxy(lastElementChild(scope.document.body)); + plugin._.setAttribute('tbCallbackId', callbackId); + + return plugin; +}; + + +// Stops and cleans up after the plugin object load timeout. +var injectObject = function injectObject (mimeType, isVisible, params, completion) { + var callbackId = generateCallbackUUID(), + plugin; + + params.onload = callbackId; + params.userAgent = OTHelpers.env.userAgent.toLowerCase(); + + scope[callbackId] = function() { + clearObjectLoadTimeout(callbackId); + + plugin._.setAttribute('id', 'tb_plugin_' + plugin._.uuid); + + if (plugin._.removeAttribute !== void 0) { + plugin._.removeAttribute('tbCallbackId'); + } + else { + // Plugin is some kind of weird object that doesn't have removeAttribute on Safari? + plugin._.tbCallbackId = null; + } + + plugin.uuid = plugin._.uuid; + plugin.id = plugin._.id; + + plugin.onReady(function(err) { + if (err) { + OTPlugin.error('Error while starting up plugin ' + plugin.uuid + ': ' + err); + return; + } + + OTPlugin.debug('Plugin ' + plugin.id + ' is loaded'); + + if (completion && OTHelpers.isFunction(completion)) { + completion.call(OTPlugin, null, plugin); + } + }); + }; + + plugin = createPluginProxy(callbackId, mimeType, params, isVisible); + + objectTimeouts[callbackId] = setTimeout(function() { + clearObjectLoadTimeout(callbackId); + + completion.call(OTPlugin, 'The object with the mimeType of ' + + mimeType + ' timed out while loading.'); + + scope.document.body.removeChild(plugin._); + }, 3000); + + return plugin; +}; + + +// tb_require('./header.js') +// tb_require('./shims.js') +// tb_require('./proxy.js') +// tb_require('./embedding.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global injectObject, scope:true */ +/* exported createMediaCaptureController:true, createPeerController:true, + injectObject:true, plugins:true, mediaCaptureObject:true, + removeAllObjects:true */ + +var objectTimeouts = {}, + mediaCaptureObject, + plugins = {}; + + +// @todo bind destroy to unload, may need to coordinate with TB +// jshint -W098 +var removeAllObjects = function removeAllObjects () { + if (mediaCaptureObject) mediaCaptureObject.destroy(); + + for (var id in plugins) { + if (plugins.hasOwnProperty(id)) { + plugins[id].destroy(); + } + } +}; + +// Creates the Media Capture controller. This exposes selectSources and is +// used in the private API. +// +// Only one Media Capture controller can exist at once, calling this method +// more than once will raise an exception. +// +var createMediaCaptureController = function createMediaCaptureController (completion) { + if (mediaCaptureObject) { + throw new Error('OTPlugin.createMediaCaptureController called multiple times!'); + } + + mediaCaptureObject = injectObject(OTPlugin.meta.mimeType, false, {windowless: false}, completion); + + mediaCaptureObject.selectSources = function() { + return this._.selectSources.apply(this._, arguments); + }; + + return mediaCaptureObject; +}; + +// Create an instance of the publisher/subscriber/peerconnection object. +// Many of these can exist at once, but the +id+ of each must be unique +// within a single instance of scope (window or window-like thing). +// +var createPeerController = function createPeerController (completion) { + var o = injectObject(OTPlugin.meta.mimeType, true, {windowless: true}, function(err, plugin) { + if (err) { + completion.call(OTPlugin, err); + return; + } + + plugins[plugin.id] = plugin; + completion.call(OTPlugin, null, plugin); + }); + + return o; +}; + +// tb_require('./header.js') +// tb_require('./shims.js') +// tb_require('./proxy.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT:true, OTPlugin:true, ActiveXObject:true, injectObject:true, curryCallAsync:true */ /* exported AutoUpdater:true */ @@ -4665,21 +5341,38 @@ var AutoUpdater; updaterMimeType, // <- cached version, use getInstallerMimeType instead installedVersion = -1; // <- cached version, use getInstallerMimeType instead - - var versionGreaterThan = function versionGreaterThan (version1,version2) { + var versionGreaterThan = function versionGreaterThan (version1, version2) { if (version1 === version2) return false; + if (version1 === -1) return version2; + if (version2 === -1) return version1; + if (version1.indexOf('.') === -1 && version2.indexOf('.') === -1) { + return version1 > version2; + } + + // The versions have multiple components (i.e. 0.10.30) and + // must be compared piecewise. + // Note: I'm ignoring the case where one version has multiple + // components and the other doesn't. var v1 = version1.split('.'), - v2 = version2.split('.'); - - v1 = parseFloat(parseInt(v1.shift(), 10) + '.' + - v1.map(function(vcomp) { return parseInt(vcomp, 10); }).join('')); - - v2 = parseFloat(parseInt(v2.shift(), 10) + '.' + - v2.map(function(vcomp) { return parseInt(vcomp, 10); }).join('')); + v2 = version2.split('.'), + versionLength = (v1.length > v2.length ? v2 : v1).length; - return v1 > v2; + for (var i = 0; i < versionLength; ++i) { + if (parseInt(v1[i], 10) > parseInt(v2[i], 10)) { + return true; + } + } + + // Special case, v1 has extra components but the initial components + // were identical, we assume this means newer but it might also mean + // that someone changed versioning systems. + if (i < v1.length) { + return true; + } + + return false; }; @@ -4692,12 +5385,12 @@ var AutoUpdater; } var activeXControlId = 'TokBox.otiePluginInstaller', + installPluginName = 'otiePluginInstaller', unversionedMimeType = 'application/x-otieplugininstaller', - plugin = navigator.plugins[activeXControlId]; + plugin = navigator.plugins[activeXControlId] || navigator.plugins[installPluginName]; installedVersion = -1; - if (plugin) { // Look through the supported mime-types for the version // There should only be one mime-type in our use case, and @@ -4705,10 +5398,11 @@ var AutoUpdater; // version. var numMimeTypes = plugin.length, extractVersion = new RegExp(unversionedMimeType.replace('-', '\\-') + - ',version=([0-9]+)', 'i'), + ',version=([0-9a-zA-Z-_.]+)', 'i'), mimeType, bits; + for (var i=0; i ', object); - } - else { - scope.OT.info('TB Plugin - ' + message); - } -}; + notifyReadyListeners = function notifyReadyListeners (err) { + var callback; - -/// Private API - -var isDomReady = function isDomReady () { - return (_document.readyState === 'complete' || - (_document.readyState === 'interactive' && _document.body)); + while ( (callback = readyCallbacks.pop()) && OTHelpers.isFunction(callback) ) { + callback.call(OTPlugin, err); + } }, onDomReady = function onDomReady () { - var callCompletionHandlers = function(err) { - var callback; - - while ( (callback = readyCallbacks.pop()) && OT.$.isFunction(callback) ) { - callback.call(TBPlugin, err); - } - }; - AutoUpdater.get(function(err, updater) { if (err) { - OT.error('Error while loading the AutoUpdater: ' + err); - callCompletionHandlers('Error while loading the AutoUpdater: ' + err); + OTPlugin.error('Error while loading the AutoUpdater: ' + err); + notifyReadyListeners('Error while loading the AutoUpdater: ' + err); return; } @@ -4948,54 +5645,51 @@ var isDomReady = function isDomReady () { err = 'The TB Plugin failed to load properly'; } - pluginReady = true; - callCompletionHandlers(err); + pluginIsReady = true; + notifyReadyListeners(err); - OT.onUnload(destroy); + OTHelpers.onDOMUnload(destroy); }); }); - }, - - waitForDomReady = function waitForDomReady () { - if (isDomReady()) { - onDomReady(); - } - else if (_document.addEventListener) { - _document.addEventListener('DOMContentLoaded', onDomReady, false); - } else if (_document.attachEvent) { - _document.attachEvent('onreadystatechange', function() { - if (_document.readyState === 'complete') onDomReady(); - }); - } - }, - - // @todo bind destroy to unload, may need to coordinate with TB - // jshint -W098 - destroy = function destroy () { - removeAllObjects(); }; +// tb_require('./header.js') +// tb_require('./shims.js') +// tb_require('./proxy.js') +// tb_require('./auto_updater.js') +// tb_require('./media_constraints.js') +// tb_require('./peer_connection.js') +// tb_require('./media_stream.js') +// tb_require('./video_container.js') +// tb_require('./rumor.js') +// tb_require('./lifecycle.js') -/// Public API +/* jshint globalstrict: true, strict: false, undef: true, + unused: true, trailing: true, browser: true, smarttabs:true */ +/* global AutoUpdater, + RumorSocket, + MediaConstraints, PeerConnection, MediaStream, + registerReadyListener, + mediaCaptureObject, createPeerController */ -TBPlugin.isInstalled = function isInstalled () { +OTPlugin.isInstalled = function isInstalled () { if (!this.isSupported()) return false; return AutoUpdater.isinstalled(); }; -TBPlugin.version = function version () { - return pluginInfo.version; +OTPlugin.version = function version () { + return OTPlugin.meta.version; }; -TBPlugin.installedVersion = function installedVersion () { +OTPlugin.installedVersion = function installedVersion () { return AutoUpdater.installedVersion(); }; -// Returns a URI to the TBPlugin installer that is paired with -// this version of TBPlugin.js. -TBPlugin.pathToInstaller = function pathToInstaller () { +// Returns a URI to the OTPlugin installer that is paired with +// this version of OTPlugin.js. +OTPlugin.pathToInstaller = function pathToInstaller () { return 'https://s3.amazonaws.com/otplugin.tokbox.com/v' + - pluginInfo.version + '/otiePluginMain.msi'; + OTPlugin.meta.version + '/otiePluginMain.msi'; }; // Trigger +callback+ when the plugin is ready @@ -5003,31 +5697,31 @@ TBPlugin.pathToInstaller = function pathToInstaller () { // Most of the public API cannot be called until // the plugin is ready. // -TBPlugin.ready = function ready (callback) { - if (TBPlugin.isReady()) { +OTPlugin.ready = function ready (callback) { + if (OTPlugin.isReady()) { var err; if (!mediaCaptureObject || !mediaCaptureObject.isValid()) { err = 'The TB Plugin failed to load properly'; } - callback.call(TBPlugin, err); + callback.call(OTPlugin, err); } else { - readyCallbacks.push(callback); + registerReadyListener(callback); } }; -// Helper function for TBPlugin.getUserMedia +// Helper function for OTPlugin.getUserMedia var _getUserMedia = function _getUserMedia(mediaConstraints, success, error) { createPeerController(function(err, plugin) { if (err) { - error.call(TBPlugin, err); + error.call(OTPlugin, err); return; } plugin._.getUserMedia(mediaConstraints.toHash(), function(streamJson) { - success.call(TBPlugin, MediaStream.fromJson(streamJson, plugin)); + success.call(OTPlugin, MediaStream.fromJson(streamJson, plugin)); }, error); }); }; @@ -5035,7 +5729,7 @@ var _getUserMedia = function _getUserMedia(mediaConstraints, success, error) { // Equivalent to: window.getUserMedia(constraints, success, error); // // Except that the constraints won't be identical -TBPlugin.getUserMedia = function getUserMedia (userConstraints, success, error) { +OTPlugin.getUserMedia = function getUserMedia (userConstraints, success, error) { var constraints = new MediaConstraints(userConstraints); if (constraints.screenSharing) { @@ -5049,7 +5743,7 @@ TBPlugin.getUserMedia = function getUserMedia (userConstraints, success, error) mediaCaptureObject.selectSources(sources, function(captureDevices) { for (var key in captureDevices) { if (captureDevices.hasOwnProperty(key)) { - OT.debug(key + ' Capture Device: ' + captureDevices[key]); + OTPlugin.debug(key + ' Capture Device: ' + captureDevices[key]); } } @@ -5062,12 +5756,12 @@ TBPlugin.getUserMedia = function getUserMedia (userConstraints, success, error) } }; -TBPlugin.initRumorSocket = function(messagingURL, completion) { - TBPlugin.ready(function(error) { +OTPlugin.initRumorSocket = function(messagingURL, completion) { + OTPlugin.ready(function(error) { if(error) { completion(error); } else { - completion(null, new PluginRumorSocket(mediaCaptureObject, messagingURL)); + completion(null, new RumorSocket(mediaCaptureObject, messagingURL)); } }); }; @@ -5076,21 +5770,26 @@ TBPlugin.initRumorSocket = function(messagingURL, completion) { // Equivalent to: var pc = new window.RTCPeerConnection(iceServers, options); // // Except that it is async and takes a completion handler -TBPlugin.initPeerConnection = function initPeerConnection (iceServers, +OTPlugin.initPeerConnection = function initPeerConnection (iceServers, options, localStream, completion) { var gotPeerObject = function(err, plugin) { if (err) { - completion.call(TBPlugin, err); + completion.call(OTPlugin, err); return; } - debug('Got PeerConnection for ' + plugin.id); - var peerConnection = new PeerConnection(iceServers, options, plugin); + OTPlugin.debug('Got PeerConnection for ' + plugin.id); + PeerConnection.create(iceServers, options, plugin, function(err, peerConnection) { + if (err) { + completion.call(OTPlugin, err); + return; + } - completion.call(TBPlugin, null, peerConnection); + completion.call(OTPlugin, null, peerConnection); + }); }; // @fixme this is nasty and brittle. We need some way to use the same Object @@ -5108,3886 +5807,380 @@ TBPlugin.initPeerConnection = function initPeerConnection (iceServers, }; // A RTCSessionDescription like object exposed for native WebRTC compatability -TBPlugin.RTCSessionDescription = function RTCSessionDescription (options) { +OTPlugin.RTCSessionDescription = function RTCSessionDescription (options) { this.type = options.type; this.sdp = options.sdp; }; // A RTCIceCandidate like object exposed for native WebRTC compatability -TBPlugin.RTCIceCandidate = function RTCIceCandidate (options) { +OTPlugin.RTCIceCandidate = function RTCIceCandidate (options) { this.sdpMid = options.sdpMid; this.sdpMLineIndex = parseInt(options.sdpMLineIndex, 10); this.candidate = options.candidate; }; +// tb_require('./api.js') -// Make this available for now -TBPlugin.debug = debug; +/* global shim, OTHelpers, onDomReady */ shim(); -waitForDomReady(); +OTHelpers.onDOMLoad(onDomReady); -// tb_require('./tb_plugin.js') /* jshint ignore:start */ })(this); /* jshint ignore:end */ -!(function() { -/*global OT:true */ - var defaultAspectRatio = 4.0/3.0, - miniWidth = 128, - miniHeight = 128, - microWidth = 64, - microHeight = 64; - // This code positions the video element so that we don't get any letterboxing. - // It will take into consideration aspect ratios other than 4/3 but only when - // the video element is first created. If the aspect ratio changes at a later point - // this calculation will become incorrect. - function fixAspectRatio(element, width, height, desiredAspectRatio, rotated) { +/* jshint ignore:start */ +!(function(window, OT) { +/* jshint ignore:end */ - if (TBPlugin.isInstalled()) { - // The plugin will sort out it's own aspect ratio, so we - // only need to tell the container to expand to fit it's parent. +// tb_require('./header.js') - OT.$.css(element, { - width: '100%', - height: '100%', - left: 0, - top: 0 - }); +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ - return; - } +// This is not obvious, so to prevent end-user frustration we'll let them know +// explicitly rather than failing with a bunch of permission errors. We don't +// handle this using an OT Exception as it's really only a development thing. +if (location.protocol === 'file:') { + /*global alert*/ + alert('You cannot test a page using WebRTC through the file system due to browser ' + + 'permissions. You must run it over a web server.'); +} - if (!width) width = parseInt(OT.$.width(element.parentNode), 10); - else width = parseInt(width, 10); - - if (!height) height = parseInt(OT.$.height(element.parentNode), 10); - else height = parseInt(height, 10); - - if (width === 0 || height === 0) return; - - if (!desiredAspectRatio) desiredAspectRatio = defaultAspectRatio; - - var actualRatio = (width + 0.0)/height, - props; - - props = { - width: '100%', - height: '100%', - left: 0, - top: 0 - }; - - if (actualRatio > desiredAspectRatio) { - // Width is largest so we blow up the height so we don't have letterboxing - var newHeight = (actualRatio / desiredAspectRatio) * 100; - - props.height = newHeight + '%'; - props.top = '-' + ((newHeight - 100) / 2) + '%'; - } else if (actualRatio < desiredAspectRatio) { - // Height is largest, blow up the width - var newWidth = (desiredAspectRatio / actualRatio) * 100; - - props.width = newWidth + '%'; - props.left = '-' + ((newWidth - 100) / 2) + '%'; - } - - OT.$.css(element, props); - - var video = element.querySelector('video'); - if(video) { - if(rotated) { - var w = element.offsetWidth, - h = element.offsetHeight, - diff = w - h; - props = { - width: h + 'px', - height: w + 'px', - marginTop: -(diff / 2) + 'px', - marginLeft: (diff / 2) + 'px' - }; - OT.$.css(video, props); - } else { - OT.$.css(video, { width: '', height: '', marginTop: '', marginLeft: ''}); - } - } - } - - function fixMini(container, width, height) { - var w = parseInt(width, 10), - h = parseInt(height, 10); - - if(w < microWidth || h < microHeight) { - OT.$.addClass(container, 'OT_micro'); - } else { - OT.$.removeClass(container, 'OT_micro'); - } - if(w < miniWidth || h < miniHeight) { - OT.$.addClass(container, 'OT_mini'); - } else { - OT.$.removeClass(container, 'OT_mini'); - } - } - - var getOrCreateContainer = function getOrCreateContainer(elementOrDomId, insertMode) { - var container, - domId; - - if (elementOrDomId && elementOrDomId.nodeName) { - // It looks like we were given a DOM element. Grab the id or generate - // one if it doesn't have one. - container = elementOrDomId; - if (!container.getAttribute('id') || container.getAttribute('id').length === 0) { - container.setAttribute('id', 'OT_' + OT.$.uuid()); - } - - domId = container.getAttribute('id'); - } else { - // We may have got an id, try and get it's DOM element. - container = OT.$(elementOrDomId); - domId = elementOrDomId || ('OT_' + OT.$.uuid()); - } - - if (!container) { - container = OT.$.createElement('div', {id: domId}); - container.style.backgroundColor = '#000000'; - document.body.appendChild(container); - } else { - if(!(insertMode == null || insertMode === 'replace')) { - var placeholder = document.createElement('div'); - placeholder.id = ('OT_' + OT.$.uuid()); - if(insertMode === 'append') { - container.appendChild(placeholder); - container = placeholder; - } else if(insertMode === 'before') { - container.parentNode.insertBefore(placeholder, container); - container = placeholder; - } else if(insertMode === 'after') { - container.parentNode.insertBefore(placeholder, container.nextSibling); - container = placeholder; - } - } else { - OT.$.emptyElement(container); - } - } - - return container; - }; - - // Creates the standard container that the Subscriber and Publisher use to hold - // their video element and other chrome. - OT.WidgetView = function(targetElement, properties) { - var container = getOrCreateContainer(targetElement, properties && properties.insertMode), - videoContainer = document.createElement('div'), - oldContainerStyles = {}, - dimensionsObserver, - videoElement, - videoObserver, - posterContainer, - loadingContainer, - width, - height, - loading = true, - audioOnly = false; - - if (properties) { - width = properties.width; - height = properties.height; - - if (width) { - if (typeof(width) === 'number') { - width = width + 'px'; - } - } - - if (height) { - if (typeof(height) === 'number') { - height = height + 'px'; - } - } - - container.style.width = width ? width : '264px'; - container.style.height = height ? height : '198px'; - container.style.overflow = 'hidden'; - fixMini(container, width || '264px', height || '198px'); - - if (properties.mirror === undefined || properties.mirror) { - OT.$.addClass(container, 'OT_mirrored'); - } - } - - if (properties.classNames) OT.$.addClass(container, properties.classNames); - OT.$.addClass(container, 'OT_loading'); - - - OT.$.addClass(videoContainer, 'OT_video-container'); - videoContainer.style.width = container.style.width; - videoContainer.style.height = container.style.height; - container.appendChild(videoContainer); - fixAspectRatio(videoContainer, container.offsetWidth, container.offsetHeight); - - loadingContainer = document.createElement('div'); - OT.$.addClass(loadingContainer, 'OT_video-loading'); - videoContainer.appendChild(loadingContainer); - - posterContainer = document.createElement('div'); - OT.$.addClass(posterContainer, 'OT_video-poster'); - videoContainer.appendChild(posterContainer); - - oldContainerStyles.width = container.offsetWidth; - oldContainerStyles.height = container.offsetHeight; - - if (!TBPlugin.isInstalled()) { - // Observe changes to the width and height and update the aspect ratio - dimensionsObserver = OT.$.observeStyleChanges(container, ['width', 'height'], - function(changeSet) { - var width = changeSet.width ? changeSet.width[1] : container.offsetWidth, - height = changeSet.height ? changeSet.height[1] : container.offsetHeight; - fixMini(container, width, height); - fixAspectRatio(videoContainer, width, height, videoElement ? - videoElement.aspectRatio() : null); - }); - - - // @todo observe if the video container or the video element get removed - // if they do we should do some cleanup - videoObserver = OT.$.observeNodeOrChildNodeRemoval(container, function(removedNodes) { - if (!videoElement) return; - - // This assumes a video element being removed is the main video element. This may - // not be the case. - var videoRemoved = OT.$.some(removedNodes, function(node) { - return node === videoContainer || node.nodeName === 'VIDEO'; - }); - - if (videoRemoved) { - videoElement.destroy(); - videoElement = null; - } - - if (videoContainer) { - OT.$.removeElement(videoContainer); - videoContainer = null; - } - - if (dimensionsObserver) { - dimensionsObserver.disconnect(); - dimensionsObserver = null; - } - - if (videoObserver) { - videoObserver.disconnect(); - videoObserver = null; - } - }); - } - - this.destroy = function() { - if (dimensionsObserver) { - dimensionsObserver.disconnect(); - dimensionsObserver = null; - } - - if (videoObserver) { - videoObserver.disconnect(); - videoObserver = null; - } - - if (videoElement) { - videoElement.destroy(); - videoElement = null; - } - - if (container) { - OT.$.removeElement(container); - container = null; - } - }; - - this.setBackgroundImageURI = function(bgImgURI) { - if (bgImgURI.substr(0, 5) !== 'http:' && bgImgURI.substr(0, 6) !== 'https:') { - if (bgImgURI.substr(0, 22) !== 'data:image/png;base64,') { - bgImgURI = 'data:image/png;base64,' + bgImgURI; - } - } - OT.$.css(posterContainer, 'backgroundImage', 'url(' + bgImgURI + ')'); - OT.$.css(posterContainer, 'backgroundSize', 'contain'); - OT.$.css(posterContainer, 'opacity', '1.0'); - }; - - if (properties && properties.style && properties.style.backgroundImageURI) { - this.setBackgroundImageURI(properties.style.backgroundImageURI); - } - - this.bindVideo = function(webRTCStream, options, completion) { - // remove the old video element if it exists - // @todo this might not be safe, publishers/subscribers use this as well... - if (videoElement) { - videoElement.destroy(); - videoElement = null; - } - - var onError = options && options.error ? options.error : void 0; - delete options.error; - - var video = new OT.VideoElement({ attributes: options }, onError); - - // Initialize the audio volume - if (options.audioVolume) video.setAudioVolume(options.audioVolume); - - // makes the incoming audio streams take priority (will impact only FF OS for now) - video.audioChannelType('telephony'); - - video.appendTo(videoContainer).bindToStream(webRTCStream, function(err) { - if (err) { - video.destroy(); - completion(err); - return; - } - - videoElement = video; - - videoElement.on({ - orientationChanged: function(){ - fixAspectRatio(videoContainer, container.offsetWidth, container.offsetHeight, - videoElement.aspectRatio(), videoElement.isRotated()); - } - }); - - var fix = function() { - fixAspectRatio(videoContainer, container.offsetWidth, container.offsetHeight, - videoElement ? videoElement.aspectRatio() : null, - videoElement ? videoElement.isRotated() : null); - }; - - if(isNaN(videoElement.aspectRatio())) { - videoElement.on('streamBound', fix); - } else { - fix(); - } - - completion(null, video); - }); - - return video; - }; - - this.video = function() { return videoElement; }; - - - OT.$.defineProperties(this, { - showPoster: { - get: function() { - return !OT.$.isDisplayNone(posterContainer); - }, - set: function(newValue) { - if(newValue) { - OT.$.show(posterContainer); - } else { - OT.$.hide(posterContainer); - } - } - }, - - poster: { - get: function() { - return OT.$.css(posterContainer, 'backgroundImage'); - }, - set: function(src) { - OT.$.css(posterContainer, 'backgroundImage', 'url(' + src + ')'); - } - }, - - loading: { - get: function() { return loading; }, - set: function(l) { - loading = l; - - if (loading) { - OT.$.addClass(container, 'OT_loading'); - } else { - OT.$.removeClass(container, 'OT_loading'); - } - } - }, - - audioOnly: { - get: function() { return audioOnly; }, - set: function(a) { - audioOnly = a; - - if (audioOnly) { - OT.$.addClass(container, 'OT_audio-only'); - } else { - OT.$.removeClass(container, 'OT_audio-only'); - } - } - }, - - domId: { - get: function() { return container.getAttribute('id'); } - } - - }); - - this.domElement = container; - - this.addError = function(errorMsg, helpMsg, classNames) { - container.innerHTML = '

' + errorMsg + - (helpMsg ? ' ' + helpMsg + '' : '') + - '

'; - OT.$.addClass(container, classNames || 'OT_subscriber_error'); - if(container.querySelector('p').offsetHeight > container.offsetHeight) { - container.querySelector('span').style.display = 'none'; - } - }; - }; - -})(window); -// Web OT Helpers -!(function(window) { - - var NativeRTCPeerConnection = (window.webkitRTCPeerConnection || - window.mozRTCPeerConnection); - - if (navigator.webkitGetUserMedia) { - /*global webkitMediaStream, webkitRTCPeerConnection*/ - // Stub for getVideoTracks for Chrome < 26 - if (!webkitMediaStream.prototype.getVideoTracks) { - webkitMediaStream.prototype.getVideoTracks = function() { - return this.videoTracks; - }; - } - - // Stubs for getAudioTracks for Chrome < 26 - if (!webkitMediaStream.prototype.getAudioTracks) { - webkitMediaStream.prototype.getAudioTracks = function() { - return this.audioTracks; - }; - } - - if (!webkitRTCPeerConnection.prototype.getLocalStreams) { - webkitRTCPeerConnection.prototype.getLocalStreams = function() { - return this.localStreams; - }; - } - - if (!webkitRTCPeerConnection.prototype.getRemoteStreams) { - webkitRTCPeerConnection.prototype.getRemoteStreams = function() { - return this.remoteStreams; - }; - } - - } else if (navigator.mozGetUserMedia) { - // Firefox < 23 doesn't support get Video/Audio tracks, we'll just stub them out for now. - /* global MediaStream */ - if (!MediaStream.prototype.getVideoTracks) { - MediaStream.prototype.getVideoTracks = function() { - return []; - }; - } - - if (!MediaStream.prototype.getAudioTracks) { - MediaStream.prototype.getAudioTracks = function() { - return []; - }; - } - - // This won't work as mozRTCPeerConnection is a weird internal Firefox - // object (a wrapped native object I think). - // if (!window.mozRTCPeerConnection.prototype.getLocalStreams) { - // window.mozRTCPeerConnection.prototype.getLocalStreams = function() { - // return this.localStreams; - // }; - // } - - // This won't work as mozRTCPeerConnection is a weird internal Firefox - // object (a wrapped native object I think). - // if (!window.mozRTCPeerConnection.prototype.getRemoteStreams) { - // window.mozRTCPeerConnection.prototype.getRemoteStreams = function() { - // return this.remoteStreams; - // }; - // } - } - - // The setEnabled method on MediaStreamTracks is a TBPlugin - // construct. In this particular instance it's easier to bring - // all the good browsers down to IE's level than bootstrap it up. - if (typeof window.MediaStreamTrack !== 'undefined') { - if (!window.MediaStreamTrack.prototype.setEnabled) { - window.MediaStreamTrack.prototype.setEnabled = function (enabled) { - this.enabled = OT.$.castToBoolean(enabled); - }; - } - } - - - OT.$.createPeerConnection = function (config, options, publishersWebRtcStream, completion) { - if (TBPlugin.isInstalled()) { - TBPlugin.initPeerConnection(config, options, - publishersWebRtcStream, completion); - } - else { - var pc; - - try { - pc = new NativeRTCPeerConnection(config, options); - } catch(e) { - completion(e.message); - return; - } - - completion(null, pc); - } - }; - - // Returns a String representing the supported WebRTC crypto scheme. The possible - // values are SDES_SRTP, DTLS_SRTP, and NONE; - // - // Broadly: - // * Firefox only supports DTLS - // * Older versions of Chrome (<= 24) only support SDES - // * Newer versions of Chrome (>= 25) support DTLS and SDES - // - OT.$.supportedCryptoScheme = function() { - if (!OT.$.hasCapabilities('webrtc')) return 'NONE'; - - var chromeVersion = window.navigator.userAgent.toLowerCase().match(/chrome\/([0-9\.]+)/i); - return chromeVersion && parseFloat(chromeVersion[1], 10) < 25 ? 'SDES_SRTP' : 'DTLS_SRTP'; - }; - -})(window); -// Web OT Helpers -!(function(window) { - - /* jshint globalstrict: true, strict: false, undef: true, unused: true, - trailing: true, browser: true, smarttabs:true */ - /* global TBPlugin, OT */ - - /// - // Capabilities - // - // Support functions to query browser/client Media capabilities. - // - - - // Indicates whether this client supports the getUserMedia - // API. - // - OT.$.registerCapability('getUserMedia', function() { - return !!(navigator.webkitGetUserMedia || navigator.mozGetUserMedia || TBPlugin.isInstalled()); - }); - - - // TODO Remove all PeerConnection stuff, that belongs to the messaging layer not the Media layer. - // Indicates whether this client supports the PeerConnection - // API. - // - // Chrome Issues: - // * The explicit prototype.addStream check is because webkitRTCPeerConnection was - // partially implemented, but not functional, in Chrome 22. - // - // Firefox Issues: - // * No real support before Firefox 19 - // * Firefox 19 has issues with generating Offers. - // * Firefox 20 doesn't interoperate with Chrome. - // - OT.$.registerCapability('PeerConnection', function() { - var browser = OT.$.browserVersion(); - - if (navigator.webkitGetUserMedia) { - return typeof(window.webkitRTCPeerConnection) === 'function' && - !!window.webkitRTCPeerConnection.prototype.addStream; - - } else if (navigator.mozGetUserMedia) { - if (typeof(window.mozRTCPeerConnection) === 'function' && browser.version > 20.0) { - try { - new window.mozRTCPeerConnection(); - return true; - } catch (err) { - return false; - } - } - } else { - return TBPlugin.isInstalled(); - } - }); - - - // Indicates whether this client supports WebRTC - // - // This is defined as: getUserMedia + PeerConnection + exceeds min browser version - // - OT.$.registerCapability('webrtc', function() { - var browser = OT.$.browserVersion(), - minimumVersions = OT.properties.minimumVersion || {}, - minimumVersion = minimumVersions[browser.browser.toLowerCase()]; - - if(minimumVersion && minimumVersion > browser.version) { - OT.debug('Support for', browser.browser, 'is disabled because we require', - minimumVersion, 'but this is', browser.version); - return false; - } - - - return OT.$.hasCapabilities('getUserMedia', 'PeerConnection'); - }); - - - // TODO Remove all transport stuff, that belongs to the messaging layer not the Media layer. - // Indicates if the browser supports bundle - // - // Broadly: - // * Firefox doesn't support bundle - // * Chrome support bundle - // * OT Plugin supports bundle - // - OT.$.registerCapability('bundle', function() { - return OT.$.hasCapabilities('webrtc') && - (OT.$.browser() === 'Chrome' || TBPlugin.isInstalled()); - }); - - - // Indicates if the browser supports rtcp mux - // - // Broadly: - // * Older versions of Firefox (<= 25) don't support rtcp mux - // * Older versions of Firefox (>= 26) support rtcp mux (not tested yet) - // * Chrome support rtcp mux - // * OT Plugin supports rtcp mux - // - OT.$.registerCapability('RTCPMux', function() { - return OT.$.hasCapabilities('webrtc') && - (OT.$.browser() === 'Chrome' || TBPlugin.isInstalled()); - }); - - - - // Indicates whether this browser supports the getMediaDevices (getSources) API. - // - OT.$.registerCapability('getMediaDevices', function() { - return OT.$.isFunction(window.MediaStreamTrack) && - OT.$.isFunction(window.MediaStreamTrack.getSources); - }); - -})(window); -// Web OT Helpers -!(function() { - - var nativeGetUserMedia, - vendorToW3CErrors, - gumNamesToMessages, - mapVendorErrorName, - parseErrorEvent, - areInvalidConstraints; - - // Handy cross-browser getUserMedia shim. Inspired by some code from Adam Barth - nativeGetUserMedia = (function() { - if (navigator.getUserMedia) { - return OT.$.bind(navigator.getUserMedia, navigator); - } else if (navigator.mozGetUserMedia) { - return OT.$.bind(navigator.mozGetUserMedia, navigator); - } else if (navigator.webkitGetUserMedia) { - return OT.$.bind(navigator.webkitGetUserMedia, navigator); - } else if (TBPlugin.isInstalled()) { - return OT.$.bind(TBPlugin.getUserMedia, TBPlugin); - } - })(); - - // Mozilla error strings and the equivalent W3C names. NOT_SUPPORTED_ERROR does not - // exist in the spec right now, so we'll include Mozilla's error description. - // Chrome TrackStartError is triggered when the camera is already used by another app (Windows) - vendorToW3CErrors = { - PERMISSION_DENIED: 'PermissionDeniedError', - NOT_SUPPORTED_ERROR: 'NotSupportedError', - MANDATORY_UNSATISFIED_ERROR: ' ConstraintNotSatisfiedError', - NO_DEVICES_FOUND: 'NoDevicesFoundError', - HARDWARE_UNAVAILABLE: 'HardwareUnavailableError', - TrackStartError: 'HardwareUnavailableError' - }; - - gumNamesToMessages = { - PermissionDeniedError: 'End-user denied permission to hardware devices', - PermissionDismissedError: 'End-user dismissed permission to hardware devices', - NotSupportedError: 'A constraint specified is not supported by the browser.', - ConstraintNotSatisfiedError: 'It\'s not possible to satisfy one or more constraints ' + - 'passed into the getUserMedia function', - OverconstrainedError: 'Due to changes in the environment, one or more mandatory ' + - 'constraints can no longer be satisfied.', - NoDevicesFoundError: 'No voice or video input devices are available on this machine.', - HardwareUnavailableError: 'The selected voice or video devices are unavailable. Verify ' + - 'that the chosen devices are not in use by another application.' - }; - - // Map vendor error strings to names and messages if possible - mapVendorErrorName = function mapVendorErrorName(vendorErrorName, vendorErrors) { - var errorName, errorMessage; - - if(vendorErrors.hasOwnProperty(vendorErrorName)) { - errorName = vendorErrors[vendorErrorName]; - } else { - // This doesn't map to a known error from the Media Capture spec, it's - // probably a custom vendor error message. - errorName = vendorErrorName; - } - - if(gumNamesToMessages.hasOwnProperty(errorName)) { - errorMessage = gumNamesToMessages[errorName]; - } else { - errorMessage = 'Unknown Error while getting user media'; - } - - return { - name: errorName, - message: errorMessage - }; - }; - - // Parse and normalise a getUserMedia error event from Chrome or Mozilla - // @ref http://dev.w3.org/2011/webrtc/editor/getusermedia.html#idl-def-NavigatorUserMediaError - parseErrorEvent = function parseErrorObject(event) { - var error; - - if (OT.$.isObject(event) && event.name) { - error = mapVendorErrorName(event.name, vendorToW3CErrors); - error.constraintName = event.constraintName; - } else if (typeof event === 'string') { - error = mapVendorErrorName(event, vendorToW3CErrors); - } else { - error = { - message: 'Unknown Error type while getting media' - }; - } - - return error; - }; - - // Validates a Hash of getUserMedia constraints. Currently we only - // check to see if there is at least one non-false constraint. - areInvalidConstraints = function(constraints) { - if (!constraints || !OT.$.isObject(constraints)) return true; - - for (var key in constraints) { - if(!constraints.hasOwnProperty(key)) { - continue; - } - if (constraints[key]) return false; - } - - return true; - }; - - - // A wrapper for the builtin navigator.getUserMedia. In addition to the usual - // getUserMedia behaviour, this helper method also accepts a accessDialogOpened - // and accessDialogClosed callback. - // - // @memberof OT.$ - // @private - // - // @param {Object} constraints - // A dictionary of constraints to pass to getUserMedia. See - // http://dev.w3.org/2011/webrtc/editor/getusermedia.html#idl-def-MediaStreamConstraints - // in the Media Capture and Streams spec for more info. - // - // @param {function} success - // Called when getUserMedia completes successfully. The callback will be passed a WebRTC - // Stream object. - // - // @param {function} failure - // Called when getUserMedia fails to access a user stream. It will be passed an object - // with a code property representing the error that occurred. - // - // @param {function} accessDialogOpened - // Called when the access allow/deny dialog is opened. - // - // @param {function} accessDialogClosed - // Called when the access allow/deny dialog is closed. - // - // @param {function} accessDenied - // Called when access is denied to the camera/mic. This will be either because - // the user has clicked deny or because a particular origin is permanently denied. - // - OT.$.getUserMedia = function(constraints, success, failure, accessDialogOpened, - accessDialogClosed, accessDenied, customGetUserMedia) { - - var getUserMedia = nativeGetUserMedia; - - if(OT.$.isFunction(customGetUserMedia)) { - getUserMedia = customGetUserMedia; - } - - // All constraints are false, we don't allow this. This may be valid later - // depending on how/if we integrate data channels. - if (areInvalidConstraints(constraints)) { - OT.error('Couldn\'t get UserMedia: All constraints were false'); - // Using a ugly dummy-code for now. - failure.call(null, { - name: 'NO_VALID_CONSTRAINTS', - message: 'Video and Audio was disabled, you need to enabled at least one' - }); - - return; - } - - var triggerOpenedTimer = null, - displayedPermissionDialog = false, - - finaliseAccessDialog = function() { - if (triggerOpenedTimer) { - clearTimeout(triggerOpenedTimer); - } - - if (displayedPermissionDialog && accessDialogClosed) accessDialogClosed(); - }, - - triggerOpened = function() { - triggerOpenedTimer = null; - displayedPermissionDialog = true; - - if (accessDialogOpened) accessDialogOpened(); - }, - - onStream = function(stream) { - finaliseAccessDialog(); - success.call(null, stream); - }, - - onError = function(event) { - finaliseAccessDialog(); - var error = parseErrorEvent(event); - - // The error name 'PERMISSION_DENIED' is from an earlier version of the spec - if (error.name === 'PermissionDeniedError' || error.name === 'PermissionDismissedError') { - accessDenied.call(null, error); - } else { - failure.call(null, error); - } - }; - - try { - getUserMedia(constraints, onStream, onError); - } catch (e) { - OT.error('Couldn\'t get UserMedia: ' + e.toString()); - onError(); - return; - } - - // The 'remember me' functionality of WebRTC only functions over HTTPS, if - // we aren't on HTTPS then we should definitely be displaying the access - // dialog. - // - // If we are on HTTPS, we'll wait 500ms to see if we get a stream - // immediately. If we do then the user had clicked 'remember me'. Otherwise - // we assume that the accessAllowed dialog is visible. - // - // @todo benchmark and see if 500ms is a reasonable number. It seems like - // we should know a lot quicker. - // - if (location.protocol.indexOf('https') === -1) { - // Execute after, this gives the client a chance to bind to the - // accessDialogOpened event. - triggerOpenedTimer = setTimeout(triggerOpened, 100); - - } else { - // wait a second and then trigger accessDialogOpened - triggerOpenedTimer = setTimeout(triggerOpened, 500); - } - }; - -})(); -// Web OT Helpers -!(function(window) { - - /* jshint globalstrict: true, strict: false, undef: true, unused: true, - trailing: true, browser: true, smarttabs:true */ - /* global OT */ - - /// - // Device Helpers - // - // Support functions to enumerating and guerying device info - // - - var chromeToW3CDeviceKinds = { - audio: 'audioInput', - video: 'videoInput' - }; - - - OT.$.shouldAskForDevices = function(callback) { - var MST = window.MediaStreamTrack; - - if(MST != null && OT.$.isFunction(MST.getSources)) { - window.MediaStreamTrack.getSources(function(sources) { - var hasAudio = sources.some(function(src) { - return src.kind === 'audio'; - }); - - var hasVideo = sources.some(function(src) { - return src.kind === 'video'; - }); - - callback.call(null, { video: hasVideo, audio: hasAudio }); - }); - - } else { - // This environment can't enumerate devices anyway, so we'll memorise this result. - OT.$.shouldAskForDevices = function(callback) { - setTimeout(OT.$.bind(callback, null, { video: true, audio: true })); - }; - - OT.$.shouldAskForDevices(callback); - } - }; - - - OT.$.getMediaDevices = function(callback) { - if(OT.$.hasCapabilities('getMediaDevices')) { - window.MediaStreamTrack.getSources(function(sources) { - var filteredSources = OT.$.filter(sources, function(source) { - return chromeToW3CDeviceKinds[source.kind] != null; - }); - callback(void 0, OT.$.map(filteredSources, function(source) { - return { - deviceId: source.id, - label: source.label, - kind: chromeToW3CDeviceKinds[source.kind] - }; - })); - }); - } else { - callback(new Error('This browser does not support getMediaDevices APIs')); - } - }; - -})(window); -(function(window) { - - var VideoOrientationTransforms = { - 0: 'rotate(0deg)', - 270: 'rotate(90deg)', - 90: 'rotate(-90deg)', - 180: 'rotate(180deg)' - }; - - OT.VideoOrientation = { - ROTATED_NORMAL: 0, - ROTATED_LEFT: 270, - ROTATED_RIGHT: 90, - ROTATED_UPSIDE_DOWN: 180 - }; - - var DefaultAudioVolume = 50; - - var DEGREE_TO_RADIANS = Math.PI * 2 / 360; - - // - // - // var _videoElement = new OT.VideoElement({ - // fallbackText: 'blah' - // }, errorHandler); - // - // _videoElement.bindToStream(webRtcStream, completion); // => VideoElement - // _videoElement.appendTo(DOMElement) // => VideoElement - // - // _videoElement.domElement // => DomNode - // - // _videoElement.imgData // => PNG Data string - // - // _videoElement.orientation = OT.VideoOrientation.ROTATED_LEFT; - // - // _videoElement.unbindStream(); - // _videoElement.destroy() // => Completely cleans up and - // removes the video element - // - // - OT.VideoElement = function(/* optional */ options/*, optional errorHandler*/) { - var _options = OT.$.defaults( options && !OT.$.isFunction(options) ? options : {}, { - fallbackText: 'Sorry, Web RTC is not available in your browser' - }), - - errorHandler = OT.$.isFunction(arguments[arguments.length-1]) ? - arguments[arguments.length-1] : void 0, - - orientationHandler = OT.$.bind(function(orientation) { - this.trigger('orientationChanged', orientation); - }, this), - - _videoElement = TBPlugin.isInstalled() ? - new PluginVideoElement(_options, errorHandler, orientationHandler) : - new NativeDOMVideoElement(_options, errorHandler, orientationHandler), - _streamBound = false, - _stream, - _preInitialisedVolue; - - OT.$.eventing(this); - - // Public Properties - OT.$.defineProperties(this, { - - domElement: { - get: function() { - return _videoElement.domElement(); - } - }, - - videoWidth: { - get: function() { - return _videoElement['video' + (this.isRotated() ? 'Height' : 'Width')](); - } - }, - - videoHeight: { - get: function() { - return _videoElement['video' + (this.isRotated() ? 'Width' : 'Height')](); - } - }, - - aspectRatio: { - get: function() { - return (this.videoWidth() + 0.0) / this.videoHeight(); - } - }, - - isRotated: { - get: function() { - return _videoElement.isRotated(); - } - }, - - orientation: { - get: function() { - return _videoElement.orientation(); - }, - set: function(orientation) { - _videoElement.orientation(orientation); - } - }, - - audioChannelType: { - get: function() { - return _videoElement.audioChannelType(); - }, - set: function(type) { - _videoElement.audioChannelType(type); - } - } - }); - - // Public Methods - - this.imgData = function() { - return _videoElement.imgData(); - }; - - this.appendTo = function(parentDomElement) { - _videoElement.appendTo(parentDomElement); - return this; - }; - - this.bindToStream = function(webRtcStream, completion) { - _streamBound = false; - _stream = webRtcStream; - - _videoElement.bindToStream(webRtcStream, OT.$.bind(function(err) { - if (err) { - completion(err); - return; - } - - _streamBound = true; - - if (_preInitialisedVolue) { - this.setAudioVolume(_preInitialisedVolue); - _preInitialisedVolue = null; - } - - completion(null); - }, this)); - - return this; - }; - - this.unbindStream = function() { - if (!_stream) return this; - - _stream = null; - _videoElement.unbindStream(); - return this; - }; - - this.setAudioVolume = function (value) { - if (_streamBound) _videoElement.setAudioVolume( OT.$.roundFloat(value / 100, 2) ); - else _preInitialisedVolue = value; - - return this; - }; - - this.getAudioVolume = function () { - if (_streamBound) return parseInt(_videoElement.getAudioVolume() * 100, 10); - else return _preInitialisedVolue || 50; - }; - - - this.whenTimeIncrements = function (callback, context) { - _videoElement.whenTimeIncrements(callback, context); - return this; - }; - - this.destroy = function () { - // unbind all events so they don't fire after the object is dead - this.off(); - - _videoElement.destroy(); - return void 0; - }; - }; - - var PluginVideoElement = function PluginVideoElement (options, - errorHandler, - orientationChangedHandler) { - var _videoProxy, - _parentDomElement; - - canBeOrientatedMixin(this, - function() { return _videoProxy.domElement; }, - orientationChangedHandler); - - /// Public methods - - this.domElement = function() { - return _videoProxy ? _videoProxy.domElement : void 0; - }; - - this.videoWidth = function() { - return _videoProxy ? _videoProxy.getVideoWidth() : void 0; - }; - - this.videoHeight = function() { - return _videoProxy ? _videoProxy.getVideoHeight() : void 0; - }; - - this.imgData = function() { - return _videoProxy ? _videoProxy.getImgData() : null; - }; - - // Append the Video DOM element to a parent node - this.appendTo = function(parentDomElement) { - _parentDomElement = parentDomElement; - return this; - }; - - // Bind a stream to the video element. - this.bindToStream = function(webRtcStream, completion) { - if (!_parentDomElement) { - completion('The VideoElement must attached to a DOM node before a stream can be bound'); - return; - } - - _videoProxy = webRtcStream._.render(); - _videoProxy.appendTo(_parentDomElement); - _videoProxy.show(completion); - - return this; - }; - - // Unbind the currently bound stream from the video element. - this.unbindStream = function() { - // TODO: some way to tell TBPlugin to release that stream and controller - - if (_videoProxy) { - _videoProxy.destroy(); - _parentDomElement = null; - _videoProxy = null; - } - - return this; - }; - - this.setAudioVolume = function(value) { - if (_videoProxy) _videoProxy.setVolume(value); - }; - - this.getAudioVolume = function() { - // Return the actual volume of the DOM element - if (_videoProxy) return _videoProxy.getVolume(); - return DefaultAudioVolume; - }; - - // see https://wiki.mozilla.org/WebAPI/AudioChannels - // The audioChannelType is not currently supported in the plugin. - this.audioChannelType = function(/* type */) { - return 'unknown'; - }; - - this.whenTimeIncrements = function(callback, context) { - // exists for compatibility with NativeVideoElement - OT.$.callAsync(OT.$.bind(callback, context)); - }; - - this.destroy = function() { - this.unbindStream(); - - return void 0; - }; - }; - - - var NativeDOMVideoElement = function NativeDOMVideoElement (options, - errorHandler, - orientationChangedHandler) { - var _domElement, - _videoElementMovedWarning = false; - - - /// Private API - var _onVideoError = OT.$.bind(function(event) { - var reason = 'There was an unexpected problem with the Video Stream: ' + - videoElementErrorCodeToStr(event.target.error.code); - errorHandler(reason, this, 'VideoElement'); - }, this), - - // The video element pauses itself when it's reparented, this is - // unfortunate. This function plays the video again and is triggered - // on the pause event. - _playVideoOnPause = function() { - if(!_videoElementMovedWarning) { - OT.warn('Video element paused, auto-resuming. If you intended to do this, ' + - 'use publishVideo(false) or subscribeToVideo(false) instead.'); - - _videoElementMovedWarning = true; - } - - _domElement.play(); - }; - - - _domElement = createNativeVideoElement(options.fallbackText, options.attributes); - - _domElement.addEventListener('pause', _playVideoOnPause); - - canBeOrientatedMixin(this, function() { return _domElement; }, orientationChangedHandler); - - /// Public methods - - this.domElement = function() { - return _domElement; - }; - - this.videoWidth = function() { - return _domElement.videoWidth; - }; - - this.videoHeight = function() { - return _domElement.videoHeight; - }; - - this.imgData = function() { - var canvas = OT.$.createElement('canvas', { - width: _domElement.videoWidth, - height: _domElement.videoHeight, - style: { display: 'none' } - }); - - document.body.appendChild(canvas); - try { - canvas.getContext('2d').drawImage(_domElement, 0, 0, canvas.width, canvas.height); - } catch(err) { - OT.warn('Cannot get image data yet'); - return null; - } - var imgData = canvas.toDataURL('image/png'); - - OT.$.removeElement(canvas); - - return OT.$.trim(imgData.replace('data:image/png;base64,', '')); - }; - - // Append the Video DOM element to a parent node - this.appendTo = function(parentDomElement) { - parentDomElement.appendChild(_domElement); - return this; - }; - - // Bind a stream to the video element. - this.bindToStream = function(webRtcStream, completion) { - bindStreamToNativeVideoElement(_domElement, webRtcStream, function(err) { - if (err) { - completion(err); - return; - } - - _domElement.addEventListener('error', _onVideoError, false); - completion(null); - }); - - return this; - }; - - - // Unbind the currently bound stream from the video element. - this.unbindStream = function() { - if (_domElement) { - unbindNativeStream(_domElement); - } - - return this; - }; - - this.setAudioVolume = function(value) { - if (_domElement) _domElement.volume = value; - }; - - this.getAudioVolume = function() { - // Return the actual volume of the DOM element - if (_domElement) return _domElement.volume; - return DefaultAudioVolume; - }; - - // see https://wiki.mozilla.org/WebAPI/AudioChannels - // The audioChannelType is currently only available in Firefox. This property returns - // "unknown" in other browser. The related HTML tag attribute is "mozaudiochannel" - this.audioChannelType = function(type) { - if (type !== void 0) { - _domElement.mozAudioChannelType = type; - } - - if ('mozAudioChannelType' in _domElement) { - return _domElement.mozAudioChannelType; - } else { - return 'unknown'; - } - }; - - this.whenTimeIncrements = function(callback, context) { - if(_domElement) { - var lastTime, handler; - handler = OT.$.bind(function() { - if(!lastTime || lastTime >= _domElement.currentTime) { - lastTime = _domElement.currentTime; - } else { - _domElement.removeEventListener('timeupdate', handler, false); - callback.call(context, this); - } - }, this); - _domElement.addEventListener('timeupdate', handler, false); - } - }; - - this.destroy = function() { - this.unbindStream(); - - if (_domElement) { - // Unbind this first, otherwise it will trigger when the - // video element is removed from the DOM. - _domElement.removeEventListener('pause', _playVideoOnPause); - - OT.$.removeElement(_domElement); - _domElement = null; - } - - return void 0; - }; - }; - -/// Private Helper functions - - // A mixin to create the orientation API implementation on +self+ - // +getDomElementCallback+ is a function that the mixin will call when it wants to - // get the native Dom element for +self+. - // - // +initialOrientation+ sets the initial orientation (shockingly), it's currently unused - // so the initial value is actually undefined. - // - var canBeOrientatedMixin = function canBeOrientatedMixin (self, - getDomElementCallback, - orientationChangedHandler, - initialOrientation) { - var _orientation = initialOrientation; - - OT.$.defineProperties(self, { - isRotated: { - get: function() { - return this.orientation() && - (this.orientation().videoOrientation === 270 || - this.orientation().videoOrientation === 90); - } - }, - - orientation: { - get: function() { return _orientation; }, - set: function(orientation) { - _orientation = orientation; - - var transform = VideoOrientationTransforms[orientation.videoOrientation] || - VideoOrientationTransforms.ROTATED_NORMAL; - - switch(OT.$.browser()) { - case 'Chrome': - case 'Safari': - getDomElementCallback().style.webkitTransform = transform; - break; - - case 'IE': - if (OT.$.browserVersion().version >= 9) { - getDomElementCallback().style.msTransform = transform; - } - else { - // So this basically defines matrix that represents a rotation - // of a single vector in a 2d basis. - // - // R = [cos(Theta) -sin(Theta)] - // [sin(Theta) cos(Theta)] - // - // Where Theta is the number of radians to rotate by - // - // Then to rotate the vector v: - // v' = Rv - // - // We then use IE8 Matrix filter property, which takes - // a 2x2 rotation matrix, to rotate our DOM element. - // - var radians = orientation.videoOrientation * DEGREE_TO_RADIANS, - element = getDomElementCallback(), - costheta = Math.cos(radians), - sintheta = Math.sin(radians); - - // element.filters.item(0).M11 = costheta; - // element.filters.item(0).M12 = -sintheta; - // element.filters.item(0).M21 = sintheta; - // element.filters.item(0).M22 = costheta; - - element.style.filter = 'progid:DXImageTransform.Microsoft.Matrix(' + - 'M11='+costheta+',' + - 'M12='+(-sintheta)+',' + - 'M21='+sintheta+',' + - 'M22='+costheta+',SizingMethod=\'auto expand\')'; - } - - - break; - - default: - // The standard version, just Firefox, Opera, and IE > 9 - getDomElementCallback().style.transform = transform; - } - - orientationChangedHandler(_orientation); - - } - }, - - // see https://wiki.mozilla.org/WebAPI/AudioChannels - // The audioChannelType is currently only available in Firefox. This property returns - // "unknown" in other browser. The related HTML tag attribute is "mozaudiochannel" - audioChannelType: { - get: function() { - if ('mozAudioChannelType' in this.domElement) { - return this.domElement.mozAudioChannelType; - } else { - return 'unknown'; - } - }, - set: function(type) { - if ('mozAudioChannelType' in this.domElement) { - this.domElement.mozAudioChannelType = type; - } - } - } - }); - }; - - function createNativeVideoElement(fallbackText, attributes) { - var videoElement = document.createElement('video'); - videoElement.setAttribute('autoplay', ''); - videoElement.innerHTML = fallbackText; - - if (attributes) { - if (attributes.muted === true) { - delete attributes.muted; - videoElement.muted = 'true'; - } - - for (var key in attributes) { - if(!attributes.hasOwnProperty(key)) { - continue; - } - videoElement.setAttribute(key, attributes[key]); - } - } - - return videoElement; - } - - - // See http://www.w3.org/TR/2010/WD-html5-20101019/video.html#error-codes - var _videoErrorCodes = {}; - - // Checking for window.MediaError for IE compatibility, just so we don't throw - // exceptions when the script is included - if (window.MediaError) { - _videoErrorCodes[window.MediaError.MEDIA_ERR_ABORTED] = 'The fetching process for the media ' + - 'resource was aborted by the user agent at the user\'s request.'; - _videoErrorCodes[window.MediaError.MEDIA_ERR_NETWORK] = 'A network error of some description ' + - 'caused the user agent to stop fetching the media resource, after the resource was ' + - 'established to be usable.'; - _videoErrorCodes[window.MediaError.MEDIA_ERR_DECODE] = 'An error of some description ' + - 'occurred while decoding the media resource, after the resource was established to be ' + - ' usable.'; - _videoErrorCodes[window.MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED] = 'The media resource ' + - 'indicated by the src attribute was not suitable.'; - } - - function videoElementErrorCodeToStr(errorCode) { - return _videoErrorCodes[parseInt(errorCode, 10)] || 'An unknown error occurred.'; - } - - function bindStreamToNativeVideoElement(videoElement, webRtcStream, completion) { - var cleanup, - onLoad, - onError, - onStoppedLoading, - timeout; - - // Note: onloadedmetadata doesn't fire in Chrome for audio only crbug.com/110938 - // After version 36 it will fire if the video track is disabled. - var browser = OT.$.browserVersion(), - needsDisabledAudioProtection = browser.browser === 'Chrome' && browser.version < 36; - - if (navigator.mozGetUserMedia || !(needsDisabledAudioProtection && - (webRtcStream.getVideoTracks().length > 0 && webRtcStream.getVideoTracks()[0].enabled))) { - - cleanup = function cleanup () { - clearTimeout(timeout); - videoElement.removeEventListener('loadedmetadata', onLoad, false); - videoElement.removeEventListener('error', onError, false); - webRtcStream.onended = null; - }; - - onLoad = function onLoad () { - cleanup(); - completion(null); - }; - - onError = function onError (event) { - cleanup(); - unbindNativeStream(videoElement); - completion('There was an unexpected problem with the Video Stream: ' + - videoElementErrorCodeToStr(event.target.error.code)); - }; - - onStoppedLoading = function onStoppedLoading () { - // The stream ended before we fully bound it. Maybe the other end called - // stop on it or something else went wrong. - cleanup(); - unbindNativeStream(videoElement); - completion('Stream ended while trying to bind it to a video element.'); - }; - - // Timeout if it takes too long - timeout = setTimeout(OT.$.bind(function() { - if (videoElement.currentTime === 0) { - cleanup(); - completion('The video stream failed to connect. Please notify the site ' + - 'owner if this continues to happen.'); - } else if (webRtcStream.ended === true) { - // The ended event should have fired by here, but support for it isn't - // always so awesome. - onStoppedLoading(); - } else { - - OT.warn('Never got the loadedmetadata event but currentTime > 0'); - onLoad(null); - } - }, this), 30000); - - videoElement.addEventListener('loadedmetadata', onLoad, false); - videoElement.addEventListener('error', onError, false); - webRtcStream.onended = onStoppedLoading; - } else { - OT.$.callAsync(completion, null); - } - - // The official spec way is 'srcObject', we are slowly converging there. - if (videoElement.srcObject !== void 0) { - videoElement.srcObject = webRtcStream; - } else if (videoElement.mozSrcObject !== void 0) { - videoElement.mozSrcObject = webRtcStream; - } else { - videoElement.src = window.URL.createObjectURL(webRtcStream); - } - - videoElement.play(); - } - - - function unbindNativeStream(videoElement) { - if (videoElement.srcObject !== void 0) { - videoElement.srcObject = null; - } else if (videoElement.mozSrcObject !== void 0) { - videoElement.mozSrcObject = null; - } else { - window.URL.revokeObjectURL(videoElement.src); - } - } - - -})(window); -// tb_require('../helpers/helpers.js') - -!(function() { - /* jshint globalstrict: true, strict: false, undef: true, unused: true, - trailing: true, browser: true, smarttabs:true */ - /* global OT */ - - var currentGuidStorage, - currentGuid; - - var isInvalidStorage = function isInvalidStorage (storageInterface) { - return !(OT.$.isFunction(storageInterface.get) && OT.$.isFunction(storageInterface.set)); - }; - - var getClientGuid = function getClientGuid (completion) { - if (currentGuid) { - completion(null, currentGuid); - return; - } - - // It's the first time that getClientGuid has been called - // in this page lifetime. Attempt to load any existing Guid - // from the storage - currentGuidStorage.get(completion); - }; - - OT.overrideGuidStorage = function (storageInterface) { - if (isInvalidStorage(storageInterface)) { - throw new Error('The storageInterface argument does not seem to be valid, ' + - 'it must implement get and set methods'); - } - - if (currentGuidStorage === storageInterface) { - return; - } - - currentGuidStorage = storageInterface; - - // If a client Guid has already been assigned to this client then - // let the new storage know about it so that it's in sync. - if (currentGuid) { - currentGuidStorage.set(currentGuid, function(error) { - if (error) { - OT.error('Failed to send initial Guid value (' + currentGuid + - ') to the newly assigned Guid Storage. The error was: ' + error); - // @todo error - } - }); - } - }; - - if (!OT._) OT._ = {}; - OT._.getClientGuid = function (completion) { - getClientGuid(function(error, guid) { - if (error) { - completion(error); - return; - } - - if (!guid) { - // Nothing came back, this client is entirely new. - // generate a new Guid and persist it - guid = OT.$.uuid(); - currentGuidStorage.set(guid, function(error) { - if (error) { - completion(error); - return; - } - - currentGuid = guid; - }); - } - else if (!currentGuid) { - currentGuid = guid; - } - - completion(null, currentGuid); - }); - }; - - - // Implement our default storage mechanism, which sets/gets a cookie - // called 'opentok_client_id' - OT.overrideGuidStorage({ - get: function(completion) { - completion(null, OT.$.getCookie('opentok_client_id')); - }, - - set: function(guid, completion) { - OT.$.setCookie('opentok_client_id', guid); - completion(null); - } - }); - -})(window); -!(function(window) { - - // Singleton interval - var logQueue = [], - queueRunning = false; - - - OT.Analytics = function() { - - var endPoint = OT.properties.loggingURL + '/logging/ClientEvent', - endPointQos = OT.properties.loggingURL + '/logging/ClientQos', - - reportedErrors = {}, - - // Map of camel-cased keys to underscored - camelCasedKeys, - - browser = OT.$.browserVersion(), - - send = function(data, isQos, callback) { - OT.$.post((isQos ? endPointQos : endPoint) + '?_=' + OT.$.uuid.v4(), { - body: data, - xdomainrequest: (browser.browser === 'IE' && browser.version < 10), - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - } - }, callback); - }, - - throttledPost = function() { - // Throttle logs so that they only happen 1 at a time - if (!queueRunning && logQueue.length > 0) { - queueRunning = true; - var curr = logQueue[0]; - - // Remove the current item and send the next log - var processNextItem = function() { - logQueue.shift(); - queueRunning = false; - throttledPost(); - }; - - if (curr) { - send(curr.data, curr.isQos, function(err) { - if(err) { - OT.debug('Failed to send ClientEvent, moving on to the next item.'); - // There was an error, move onto the next item - } else { - curr.onComplete(); - } - setTimeout(processNextItem, 50); - }); - } - } - }, - - post = function(data, onComplete, isQos) { - logQueue.push({ - data: data, - onComplete: onComplete, - isQos: isQos - }); - - throttledPost(); - }, - - shouldThrottleError = function(code, type, partnerId) { - if (!partnerId) return false; - - var errKey = [partnerId, type, code].join('_'), - //msgLimit = DynamicConfig.get('exceptionLogging', 'messageLimitPerPartner', partnerId); - msgLimit = 100; - if (msgLimit === null || msgLimit === undefined) return false; - return (reportedErrors[errKey] || 0) <= msgLimit; - }; - - camelCasedKeys = { - payloadType: 'payload_type', - partnerId: 'partner_id', - streamId: 'stream_id', - sessionId: 'session_id', - connectionId: 'connection_id', - widgetType: 'widget_type', - widgetId: 'widget_id', - avgAudioBitrate: 'avg_audio_bitrate', - avgVideoBitrate: 'avg_video_bitrate', - localCandidateType: 'local_candidate_type', - remoteCandidateType: 'remote_candidate_type', - transportType: 'transport_type' - }; - - // Log an error via ClientEvents. - // - // @param [String] code - // @param [String] type - // @param [String] message - // @param [Hash] details additional error details - // - // @param [Hash] options the options to log the client event with. - // @option options [String] action The name of the Event that we are logging. E.g. - // 'TokShowLoaded'. Required. - // @option options [String] variation Usually used for Split A/B testing, when you - // have multiple variations of the +_action+. - // @option options [String] payloadType A text description of the payload. Required. - // @option options [String] payload The payload. Required. - // @option options [String] sessionId The active OpenTok session, if there is one - // @option options [String] connectionId The active OpenTok connectionId, if there is one - // @option options [String] partnerId - // @option options [String] guid ... - // @option options [String] widgetId ... - // @option options [String] streamId ... - // @option options [String] section ... - // @option options [String] build ... - // - // Reports will be throttled to X reports (see exceptionLogging.messageLimitPerPartner - // from the dynamic config for X) of each error type for each partner. Reports can be - // disabled/enabled globally or on a per partner basis (per partner settings - // take precedence) using exceptionLogging.enabled. - // - this.logError = function(code, type, message, details, options) { - if (!options) options = {}; - var partnerId = options.partnerId; - - if (OT.Config.get('exceptionLogging', 'enabled', partnerId) !== true) { - return; - } - - if (shouldThrottleError(code, type, partnerId)) { - //OT.log('ClientEvents.error has throttled an error of type ' + type + '.' + - // code + ' for partner ' + (partnerId || 'No Partner Id')); - return; - } - - var errKey = [partnerId, type, code].join('_'), - - payload = this.escapePayload(OT.$.extend(details || {}, { - message: payload, - userAgent: OT.$.userAgent() - })); - - - reportedErrors[errKey] = typeof(reportedErrors[errKey]) !== 'undefined' ? - reportedErrors[errKey] + 1 : 1; - - return this.logEvent(OT.$.extend(options, { - action: type + '.' + code, - payloadType: payload[0], - payload: payload[1] - })); - }; - - // Log a client event to the analytics backend. - // - // @example Logs a client event called 'foo' - // OT.ClientEvents.log({ - // action: 'foo', - // payload_type: 'foo's payload', - // payload: 'bar', - // session_id: sessionId, - // connection_id: connectionId - // }) - // - // @param [Hash] options the options to log the client event with. - // @option options [String] action The name of the Event that we are logging. - // E.g. 'TokShowLoaded'. Required. - // @option options [String] variation Usually used for Split A/B testing, when - // you have multiple variations of the +_action+. - // @option options [String] payloadType A text description of the payload. Required. - // @option options [String] payload The payload. Required. - // @option options [String] session_id The active OpenTok session, if there is one - // @option options [String] connection_id The active OpenTok connectionId, if there is one - // @option options [String] partner_id - // @option options [String] guid ... - // @option options [String] widget_id ... - // @option options [String] stream_id ... - // @option options [String] section ... - // @option options [String] build ... - // - this.logEvent = function(options) { - var partnerId = options.partnerId; - - if (!options) options = {}; - - OT._.getClientGuid(function(error, guid) { - if (error) { - // @todo - return; - } - - // Set a bunch of defaults - var data = OT.$.extend({ - 'variation' : '', - 'guid' : guid, - 'widget_id' : '', - 'session_id': '', - 'connection_id': '', - 'stream_id' : '', - 'partner_id' : partnerId, - 'source' : window.location.href, - 'section' : '', - 'build' : '' - }, options), - - onComplete = function(){ - // OT.log('logged: ' + '{action: ' + data['action'] + ', variation: ' + data['variation'] - // + ', payload_type: ' + data['payload_type'] + ', payload: ' + data['payload'] + '}'); - }; - - // We camel-case our names, but the ClientEvents backend wants them - // underscored... - for (var key in camelCasedKeys) { - if (camelCasedKeys.hasOwnProperty(key) && data[key]) { - data[camelCasedKeys[key]] = data[key]; - delete data[key]; - } - } - - post(data, onComplete, false); - }); - }; - - // Log a client QOS to the analytics backend. - // - this.logQOS = function(options) { - var partnerId = options.partnerId; - - if (!options) options = {}; - - OT._.getClientGuid(function(error, guid) { - if (error) { - // @todo - return; - } - - // Set a bunch of defaults - var data = OT.$.extend({ - 'guid' : guid, - 'widget_id' : '', - 'session_id': '', - 'connection_id': '', - 'stream_id' : '', - 'partner_id' : partnerId, - 'source' : window.location.href, - 'build' : '', - 'duration' : 0 //in milliseconds - }, options), - - onComplete = function(){ - // OT.log('logged: ' + '{action: ' + data['action'] + ', variation: ' + data['variation'] - // + ', payload_type: ' + data['payload_type'] + ', payload: ' + data['payload'] + '}'); - }; - - // We camel-case our names, but the ClientEvents backend wants them - // underscored... - for (var key in camelCasedKeys) { - if (camelCasedKeys.hasOwnProperty(key)) { - if(data[key]) { - data[camelCasedKeys[key]] = data[key]; - } - delete data[key]; - } - } - - post(data, onComplete, true); - }); - }; - - // Converts +payload+ to two pipe seperated strings. Doesn't currently handle - // edgecases, e.g. escaping '\\|' will break stuff. - // - // *Note:* It strip any keys that have null values. - this.escapePayload = function(payload) { - var escapedPayload = [], - escapedPayloadDesc = []; - - for (var key in payload) { - if (payload.hasOwnProperty(key) && payload[key] !== null && payload[key] !== undefined) { - escapedPayload.push( payload[key] ? payload[key].toString().replace('|', '\\|') : '' ); - escapedPayloadDesc.push( key.toString().replace('|', '\\|') ); - } - } - - return [ - escapedPayloadDesc.join('|'), - escapedPayload.join('|') - ]; - }; - }; - -})(window); -!(function() { - - OT.$.registerCapability('audioOutputLevelStat', function() { - return OT.$.browserVersion().browser === 'Chrome'; - }); - - OT.$.registerCapability('webAudioCapableRemoteStream', function() { - return OT.$.browserVersion().browser === 'Firefox'; - }); - - OT.$.registerCapability('getStatsWithSingleParameter', function() { - return OT.$.browserVersion().browser === 'Chrome'; - }); - - OT.$.registerCapability('webAudio', function() { - return 'AudioContext' in window; - }); - -})(); -!(function(window) { - - // This is not obvious, so to prevent end-user frustration we'll let them know - // explicitly rather than failing with a bunch of permission errors. We don't - // handle this using an OT Exception as it's really only a development thing. - if (location.protocol === 'file:') { - /*global alert*/ - alert('You cannot test a page using WebRTC through the file system due to browser ' + - 'permissions. You must run it over a web server.'); - } - - if (!window.OT) window.OT = {}; - - if (!window.URL && window.webkitURL) { - window.URL = window.webkitURL; - } - - var _analytics = new OT.Analytics(); - - var // Global parameters used by upgradeSystemRequirements - _intervalId, - _lastHash = document.location.hash; - - -/** -* The first step in using the OpenTok API is to call the OT.initSession() -* method. Other methods of the OT object check for system requirements and set up error logging. -* -* @class OT -*/ - -/** -*

-* Initializes and returns the local session object for a specified session ID. -*

-*

-* You connect to an OpenTok session using the connect() method -* of the Session object returned by the OT.initSession() method. -* Note that calling OT.initSession() does not initiate communications -* with the cloud. It simply initializes the Session object that you can use to -* connect (and to perform other operations once connected). -*

-* -*

-* For an example, see Session.connect(). -*

-* -* @method OT.initSession -* @memberof OT -* @param {String} apiKey Your OpenTok API key (see the -* OpenTok dashboard). -* @param {String} sessionId The session ID identifying the OpenTok session. For more -* information, see Session creation. -* @returns {Session} The session object through which all further interactions with -* the session will occur. -*/ - OT.initSession = function(apiKey, sessionId) { - - if(sessionId == null) { - sessionId = apiKey; - apiKey = null; - } - - var session = OT.sessions.get(sessionId); - - if (!session) { - session = new OT.Session(apiKey, sessionId); - OT.sessions.add(session); - } - - return session; - }; - -/** -*

-* Initializes and returns a Publisher object. You can then pass this Publisher -* object to Session.publish() to publish a stream to a session. -*

-*

-* Note: If you intend to reuse a Publisher object created using -* OT.initPublisher() to publish to different sessions sequentially, -* call either Session.disconnect() or Session.unpublish(). -* Do not call both. Then call the preventDefault() method of the -* streamDestroyed or sessionDisconnected event object to prevent the -* Publisher object from being removed from the page. -*

-* -* @param {Object} targetElement (Optional) The DOM element or the id attribute of the -* existing DOM element used to determine the location of the Publisher video in the HTML DOM. See -* the insertMode property of the properties parameter. If you do not -* specify a targetElement, the application appends a new DOM element to the HTML -* body. -* -*

-* The application throws an error if an element with an ID set to the -* targetElement value does not exist in the HTML DOM. -*

-* -* @param {Object} properties (Optional) This object contains the following properties (each of which -* are optional): -*

-*
    -*
  • -* audioSource (String) — The ID of the audio input device (such as a -* microphone) to be used by the publisher. You can obtain a list of available devices, including -* audio input devices, by calling the OT.getDevices() method. Each -* device listed by the method has a unique device ID. If you pass in a device ID that does not -* match an existing audio input device, the call to OT.initPublisher() fails with an -* error (error code 1500, "Unable to Publish") passed to the completion handler function. -*
  • -*
  • -* frameRate (Number) — The desired frame rate, in frames per second, -* of the video. Valid values are 30, 15, 7, and 1. The published stream will use the closest -* value supported on the publishing client. The frame rate can differ slightly from the value -* you set, depending on the browser of the client. And the video will only use the desired -* frame rate if the client configuration supports it. -*

    If the publisher specifies a frame rate, the actual frame rate of the video stream -* is set as the frameRate property of the Stream object, though the actual frame rate -* will vary based on changing network and system conditions. If the developer does not specify a -* frame rate, this property is undefined. -*

    -* For sessions that use the OpenTok Media Router (sessions with -* the media mode -* set to routed, lowering the frame rate or lowering the resolution reduces -* the maximum bandwidth the stream can use. However, in sessions with the media mode set to -* relayed, lowering the frame rate or resolution may not reduce the stream's bandwidth. -*

    -*

    -* You can also restrict the frame rate of a Subscriber's video stream. To restrict the frame rate -* a Subscriber, call the restrictFrameRate() method of the subscriber, passing in -* true. -* (See Subscriber.restrictFrameRate().) -*

    -*
  • -*
  • -* height (Number) — The desired height, in pixels, of the -* displayed Publisher video stream (default: 198). Note: Use the -* height and width properties to set the dimensions -* of the publisher video; do not set the height and width of the DOM element -* (using CSS). -*
  • -*
  • -* insertMode (String) — Specifies how the Publisher object will be -* inserted in the HTML DOM. See the targetElement parameter. This string can -* have the following values: -*
      -*
    • "replace" — The Publisher object replaces contents of the -* targetElement. This is the default.
    • -*
    • "after" — The Publisher object is a new element inserted after -* the targetElement in the HTML DOM. (Both the Publisher and targetElement have the -* same parent element.)
    • -*
    • "before" — The Publisher object is a new element inserted before -* the targetElement in the HTML DOM. (Both the Publisher and targetElement have the same -* parent element.)
    • -*
    • "append" — The Publisher object is a new element added as a child -* of the targetElement. If there are other child elements, the Publisher is appended as -* the last child element of the targetElement.
    • -*
    -*
  • -*
  • -* mirror (Boolean) — Whether the publisher's video image -* is mirrored in the publisher's page<. The default value is true -* (the video image is mirrored). This property does not affect the display -* on other subscribers' web pages. -*
  • -*
  • -* name (String) — The name for this stream. The name appears at -* the bottom of Subscriber videos. The default value is "" (an empty string). Setting -* this to a string longer than 1000 characters results in an runtime exception. -*
  • -*
  • -* publishAudio (Boolean) — Whether to initially publish audio -* for the stream (default: true). This setting applies when you pass -* the Publisher object in a call to the Session.publish() method. -*
  • -*
  • -* publishVideo (Boolean) — Whether to initially publish video -* for the stream (default: true). This setting applies when you pass -* the Publisher object in a call to the Session.publish() method. -*
  • -*
  • -* resolution (String) — The desired resolution of the video. The format -* of the string is "widthxheight", where the width and height are represented in -* pixels. Valid values are "1280x720", "640x480", and -* "320x240". The published video will only use the desired resolution if the -* client configuration supports it. -*

    -* The requested resolution of a video stream is set as the videoDimensions.width and -* videoDimensions.height properties of the Stream object. -*

    -*

    -* The default resolution for a stream (if you do not specify a resolution) is 640x480 pixels. -* If the client system cannot support the resolution you requested, the the stream will use the -* next largest setting supported. -*

    -*

    -* For sessions that use the OpenTok Media Router (sessions with the -* media mode -* set to routed, lowering the frame rate or lowering the resolution reduces the maximum bandwidth -* the stream can use. However, in sessions that have the media mode set to relayed, lowering the -* frame rate or resolution may not reduce the stream's bandwidth. -*

    -*
  • -*
  • -* style (Object) — An object containing properties that define the initial -* appearance of user interface controls of the Publisher. The style object includes -* the following properties: -*
      -*
    • audioLevelDisplayMode (String) — How to display the audio level -* indicator. Possible values are: "auto" (the indicator is displayed when the -* video is disabled), "off" (the indicator is not displayed), and -* "on" (the indicator is always displayed).
    • -* -*
    • backgroundImageURI (String) — A URI for an image to display as -* the background image when a video is not displayed. (A video may not be displayed if -* you call publishVideo(false) on the Publisher object). You can pass an http -* or https URI to a PNG, JPEG, or non-animated GIF file location. You can also use the -* data URI scheme (instead of http or https) and pass in base-64-encrypted -* PNG data, such as that obtained from the -* Publisher.getImgData() method. For example, -* you could set the property to "data:VBORw0KGgoAA...", where the portion of the -* string after "data:" is the result of a call to -* Publisher.getImgData(). If the URL or the image data is invalid, the property -* is ignored (the attempt to set the image fails silently). -*

      -* Note that in Internet Explorer 8 (using the OpenTok Plugin for Internet Explorer), -* you cannot set the backgroundImageURI style to a string larger than 32 kB. -* This is due to an IE 8 limitation on the size of URI strings. Due to this limitation, -* you cannot set the backgroundImageURI style to a string obtained with the -* getImgData() method. -*

    • -* -*
    • buttonDisplayMode (String) — How to display the microphone controls -* Possible values are: "auto" (controls are displayed when the stream is first -* displayed and when the user mouses over the display), "off" (controls are not -* displayed), and "on" (controls are always displayed).
    • -* -*
    • nameDisplayMode (String) — Whether to display the stream name. -* Possible values are: "auto" (the name is displayed when the stream is first -* displayed and when the user mouses over the display), "off" (the name is not -* displayed), and "on" (the name is always displayed).
    • -*
    -*
  • -*
  • -* videoSource (String) — The ID of the video input device (such as a -* camera) to be used by the publisher. You can obtain a list of available devices, including -* video input devices, by calling the OT.getDevices() method. Each -* device listed by the method has a unique device ID. If you pass in a device ID that does not -* match an existing video input device, the call to OT.initPublisher() fails with an -* error (error code 1500, "Unable to Publish") passed to the completion handler function. -*
  • -*
  • -* width (Number) — The desired width, in pixels, of the -* displayed Publisher video stream (default: 264). Note: Use the -* height and width properties to set the dimensions -* of the publisher video; do not set the height and width of the DOM element -* (using CSS). -*
  • -*
-* @param {Function} completionHandler (Optional) A function to be called when the method succeeds -* or fails in initializing a Publisher object. This function takes one parameter — -* error. On success, the error object is set to null. On -* failure, the error object has two properties: code (an integer) and -* message (a string), which identify the cause of the failure. The method succeeds -* when the user grants access to the camera and microphone. The method fails if the user denies -* access to the camera and microphone. The completionHandler function is called -* before the Publisher dispatches an accessAllowed (success) event or an -* accessDenied (failure) event. -*

-* The following code adds a completionHandler when calling the -* OT.initPublisher() method: -*

-*
-* var publisher = OT.initPublisher('publisher', null, function (error) {
-*   if (error) {
-*     console.log(error);
-*   } else {
-*     console.log("Publisher initialized.");
-*   }
-* });
-* 
-* -* @returns {Publisher} The Publisher object. -* @see for audio input - * devices or "videoInput" for video input devices. - *

- * The deviceId property is a unique ID for the device. You can pass - * the deviceId in as the audioSource or videoSource - * property of the the options parameter of the - * OT.initPublisher() method. - *

- * The label property identifies the device. The label - * property is set to an empty string if the user has not previously granted access to - * a camera and microphone. In HTTP, the user must have granted access to a camera and - * microphone in the current page (for example, in response to a call to - * OT.initPublisher()). In HTTPS, the user must have previously granted access - * to the camera and microphone in the current page or in a page previously loaded from the - * domain. - * - * - * @see OT.initPublisher() - * @method OT.getDevices - * @memberof OT - */ - OT.getDevices = function(callback) { - OT.$.getMediaDevices(callback); - }; - - -/** -* Checks if the system supports OpenTok for WebRTC. -* @return {Number} Whether the system supports OpenTok for WebRTC (1) or not (0). -* @see OT.upgradeSystemRequirements() -* @method OT.checkSystemRequirements -* @memberof OT -*/ - OT.checkSystemRequirements = function() { - OT.debug('OT.checkSystemRequirements()'); - - // Try native support first, then TBPlugin... - var systemRequirementsMet = OT.$.hasCapabilities('websockets', 'webrtc') || - TBPlugin.isInstalled(); - - systemRequirementsMet = systemRequirementsMet ? - this.HAS_REQUIREMENTS : this.NOT_HAS_REQUIREMENTS; - - OT.checkSystemRequirements = function() { - OT.debug('OT.checkSystemRequirements()'); - return systemRequirementsMet; - }; - - if(systemRequirementsMet === this.NOT_HAS_REQUIREMENTS) { - _analytics.logEvent({ - action: 'checkSystemRequirements', - variation: 'notHasRequirements', - 'payload_type': 'userAgent', - 'partner_id': OT.APIKEY, - payload: OT.$.userAgent() - }); - } - - return systemRequirementsMet; - }; - - -/** -* Displays information about system requirments for OpenTok for WebRTC. This -* information is displayed in an iframe element that fills the browser window. -*

-* Note: this information is displayed automatically when you call the -* OT.initSession() or the OT.initPublisher() method -* if the client does not support OpenTok for WebRTC. -*

-* @see OT.checkSystemRequirements() -* @method OT.upgradeSystemRequirements -* @memberof OT -*/ - OT.upgradeSystemRequirements = function(){ - // trigger after the OT environment has loaded - OT.onLoad( function() { - - if(TBPlugin.isSupported()) { - OT.Dialogs.Plugin.promptToInstall().on({ - download: function() { - window.location = TBPlugin.pathToInstaller(); - }, - refresh: function() { - location.reload(); - }, - closed: function() {} - }); - return; - } - - var id = '_upgradeFlash'; - - // Load the iframe over the whole page. - document.body.appendChild((function() { - var d = document.createElement('iframe'); - d.id = id; - d.style.position = 'absolute'; - d.style.position = 'fixed'; - d.style.height = '100%'; - d.style.width = '100%'; - d.style.top = '0px'; - d.style.left = '0px'; - d.style.right = '0px'; - d.style.bottom = '0px'; - d.style.zIndex = 1000; - try { - d.style.backgroundColor = 'rgba(0,0,0,0.2)'; - } catch (err) { - // Old IE browsers don't support rgba and we still want to show the upgrade message - // but we just make the background of the iframe completely transparent. - d.style.backgroundColor = 'transparent'; - d.setAttribute('allowTransparency', 'true'); - } - d.setAttribute('frameBorder', '0'); - d.frameBorder = '0'; - d.scrolling = 'no'; - d.setAttribute('scrolling', 'no'); - - var browser = OT.$.browserVersion(), - minimumBrowserVersion = OT.properties.minimumVersion[browser.browser.toLowerCase()], - isSupportedButOld = minimumBrowserVersion > browser.version; - d.src = OT.properties.assetURL + '/html/upgrade.html#' + - encodeURIComponent(isSupportedButOld ? 'true' : 'false') + ',' + - encodeURIComponent(JSON.stringify(OT.properties.minimumVersion)) + '|' + - encodeURIComponent(document.location.href); - - return d; - })()); - - // Now we need to listen to the event handler if the user closes this dialog. - // Since this is from an IFRAME within another domain we are going to listen to hash - // changes. The best cross browser solution is to poll for a change in the hashtag. - if (_intervalId) clearInterval(_intervalId); - _intervalId = setInterval(function(){ - var hash = document.location.hash, - re = /^#?\d+&/; - if (hash !== _lastHash && re.test(hash)) { - _lastHash = hash; - if (hash.replace(re, '') === 'close_window'){ - document.body.removeChild(document.getElementById(id)); - document.location.hash = ''; - } - } - }, 100); - }); - }; - - - OT.reportIssue = function(){ - OT.warn('ToDo: haven\'t yet implemented OT.reportIssue'); - }; - - OT.components = {}; - OT.sessions = {}; - - // namespaces - OT.rtc = {}; +var OT = window.OT || {}; // Define the APIKEY this is a global parameter which should not change - OT.APIKEY = (function(){ - // Script embed - var scriptSrc = (function(){ - var s = document.getElementsByTagName('script'); - s = s[s.length - 1]; - s = s.getAttribute('src') || s.src; - return s; - })(); - - var m = scriptSrc.match(/[\?\&]apikey=([^&]+)/i); - return m ? m[1] : ''; +OT.APIKEY = (function(){ + // Script embed + var scriptSrc = (function(){ + var s = document.getElementsByTagName('script'); + s = s[s.length - 1]; + s = s.getAttribute('src') || s.src; + return s; })(); - OT.HAS_REQUIREMENTS = 1; - OT.NOT_HAS_REQUIREMENTS = 0; + var m = scriptSrc.match(/[\?\&]apikey=([^&]+)/i); + return m ? m[1] : ''; +})(); -/** -* This method is deprecated. Use on() or once() instead. -* -*

-* Registers a method as an event listener for a specific event. -*

-* -*

-* The OT object dispatches one type of event — an exception event. The -* following code adds an event listener for the exception event: -*

-* -*
-* OT.addEventListener("exception", exceptionHandler);
-*
-* function exceptionHandler(event) {
-*    alert("exception event. \n  code == " + event.code + "\n  message == " + event.message);
-* }
-* 
-* -*

-* If a handler is not registered for an event, the event is ignored locally. If the event -* listener function does not exist, the event is ignored locally. -*

-*

-* Throws an exception if the listener name is invalid. -*

-* -* @param {String} type The string identifying the type of event. -* -* @param {Function} listener The function to be invoked when the OT object dispatches the event. -* @see on() -* @see once() -* @memberof OT -* @method addEventListener -*/ -/** -* This method is deprecated. Use off() instead. -* -*

-* Removes an event listener for a specific event. -*

-* -*

-* Throws an exception if the listener name is invalid. -*

-* -* @param {String} type The string identifying the type of event. -* -* @param {Function} listener The event listener function to remove. -* -* @see off() -* @memberof OT -* @method removeEventListener -*/ +if (!window.OT) window.OT = OT; +if (!window.TB) window.TB = OT; + +// tb_require('../js/ot.js') + +OT.properties = { + version: 'v2.4.0', // The current version (eg. v2.0.4) (This is replaced by gradle) + build: '54ae164', // The current build hash (This is replaced by gradle) + + // Whether or not to turn on debug logging by default + debug: 'false', + // The URL of the tokbox website + websiteURL: 'http://www.tokbox.com', + + // The URL of the CDN + cdnURL: 'http://static.opentok.com', + // The URL to use for logging + loggingURL: 'http://hlg.tokbox.com/prod', + + // The anvil API URL + apiURL: 'http://anvil.opentok.com', + + // What protocol to use when connecting to the rumor web socket + messagingProtocol: 'wss', + // What port to use when connection to the rumor web socket + messagingPort: 443, + + // If this environment supports SSL + supportSSL: 'true', + // The CDN to use if we're using SSL + cdnURLSSL: 'https://static.opentok.com', + // The URL to use for logging + loggingURLSSL: 'https://hlg.tokbox.com/prod', + + // The anvil API URL to use if we're using SSL + apiURLSSL: 'https://anvil.opentok.com', + + minimumVersion: { + firefox: parseFloat('29'), + chrome: parseFloat('34') + } +}; + + +// tb_require('../ot.js') +// tb_require('../../conf/properties.js'); + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ + + +// Mount OTHelpers on OT.$ +OT.$ = window.OTHelpers; + +// Allow events to be bound on OT +OT.$.eventing(OT); + +// REMOVE THIS POST IE MERGE + +OT.$.defineGetters = function(self, getters, enumerable) { + var propsDefinition = {}; + + if (enumerable === void 0) enumerable = false; + + for (var key in getters) { + if(!getters.hasOwnProperty(key)) { + continue; + } + + propsDefinition[key] = { + get: getters[key], + enumerable: enumerable + }; + } + + Object.defineProperties(self, propsDefinition); +}; + +// STOP REMOVING HERE + +// OT.$.Modal was OT.Modal before the great common-js-helpers move +OT.Modal = OT.$.Modal; + +// Add logging methods +OT.$.useLogHelpers(OT); + +var _debugHeaderLogged = false, + _setLogLevel = OT.setLogLevel; + +// On the first time log level is set to DEBUG (or higher) show version info. +OT.setLogLevel = function(level) { + // Set OT.$ to the same log level + OT.$.setLogLevel(level); + var retVal = _setLogLevel.call(OT, level); + if (OT.shouldLog(OT.DEBUG) && !_debugHeaderLogged) { + OT.debug('OpenTok JavaScript library ' + OT.properties.version); + OT.debug('Release notes: ' + OT.properties.websiteURL + + '/opentok/webrtc/docs/js/release-notes.html'); + OT.debug('Known issues: ' + OT.properties.websiteURL + + '/opentok/webrtc/docs/js/release-notes.html#knownIssues'); + _debugHeaderLogged = true; + } + OT.debug('OT.setLogLevel(' + retVal + ')'); + return retVal; +}; + +var debugTrue = OT.properties.debug === 'true' || OT.properties.debug === true; +OT.setLogLevel(debugTrue ? OT.DEBUG : OT.ERROR); + + +// Patch the userAgent to ref OTPlugin, if it's installed. +if (OTPlugin && OTPlugin.isInstalled()) { + OT.$.env.userAgent += '; OTPlugin ' + OTPlugin.version(); +} + +// @todo remove this +OT.$.userAgent = function() { return OT.$.env.userAgent; }; /** -* Adds an event handler function for one or more events. -* -*

-* The OT object dispatches one type of event — an exception event. The following -* code adds an event -* listener for the exception event: -*

-* -*
-* OT.on("exception", function (event) {
-*   // This is the event handler.
-* });
-* 
-* -*

You can also pass in a third context parameter (which is optional) to define the -* value of -* this in the handler method:

-* -*
-* OT.on("exception",
-*   function (event) {
-*     // This is the event handler.
-*   }),
-*   session
-* );
-* 
-* -*

-* If you do not add a handler for an event, the event is ignored locally. -*

-* -* @param {String} type The string identifying the type of event. -* @param {Function} handler The handler function to process the event. This function takes the event -* object as a parameter. -* @param {Object} context (Optional) Defines the value of this in the event handler -* function. -* -* @memberof OT -* @method on -* @see off() -* @see once() -* @see Events -*/ + * Sets the API log level. + *

+ * Calling OT.setLogLevel() sets the log level for runtime log messages that + * are the OpenTok library generates. The default value for the log level is OT.ERROR. + *

+ *

+ * The OpenTok JavaScript library displays log messages in the debugger console (such as + * Firebug), if one exists. + *

+ *

+ * The following example logs the session ID to the console, by calling OT.log(). + * The code also logs an error message when it attempts to publish a stream before the Session + * object dispatches a sessionConnected event. + *

+ *
+  * OT.setLogLevel(OT.LOG);
+  * session = OT.initSession(sessionId);
+  * OT.log(sessionId);
+  * publisher = OT.initPublisher("publishContainer");
+  * session.publish(publisher);
+  * 
+ * + * @param {Number} logLevel The degree of logging desired by the developer: + * + *

+ *

    + *
  • + * OT.NONE — API logging is disabled. + *
  • + *
  • + * OT.ERROR — Logging of errors only. + *
  • + *
  • + * OT.WARN — Logging of warnings and errors. + *
  • + *
  • + * OT.INFO — Logging of other useful information, in addition to + * warnings and errors. + *
  • + *
  • + * OT.LOG — Logging of OT.log() messages, in addition + * to OpenTok info, warning, + * and error messages. + *
  • + *
  • + * OT.DEBUG — Fine-grained logging of all API actions, as well as + * OT.log() messages. + *
  • + *
+ *

+ * + * @name OT.setLogLevel + * @memberof OT + * @function + * @see OT.log() + */ /** -* Adds an event handler function for an event. Once the handler is called, the specified handler -* method is -* removed as a handler for this event. (When you use the OT.on() method to add an event -* handler, the handler -* is not removed when it is called.) The OT.once() method is the equivilent of -* calling the OT.on() -* method and calling OT.off() the first time the handler is invoked. -* -*

-* The following code adds a one-time event handler for the exception event: -*

-* -*
-* OT.once("exception", function (event) {
-*   console.log(event);
-* }
-* 
-* -*

You can also pass in a third context parameter (which is optional) to define the -* value of -* this in the handler method:

-* -*
-* OT.once("exception",
-*   function (event) {
-*     // This is the event handler.
-*   },
-*   session
-* );
-* 
-* -*

-* The method also supports an alternate syntax, in which the first parameter is an object that is a -* hash map of -* event names and handler functions and the second parameter (optional) is the context for this in -* each handler: -*

-*
-* OT.once(
-*   {exeption: function (event) {
-*     // This is the event handler.
-*     }
-*   },
-*   session
-* );
-* 
-* -* @param {String} type The string identifying the type of event. You can specify multiple event -* names in this string, -* separating them with a space. The event handler will process the first occurence of the events. -* After the first event, -* the handler is removed (for all specified events). -* @param {Function} handler The handler function to process the event. This function takes the event -* object as a parameter. -* @param {Object} context (Optional) Defines the value of this in the event handler -* function. -* -* @memberof OT -* @method once -* @see on() -* @see once() -* @see Events -*/ + * Sends a string to the the debugger console (such as Firebug), if one exists. + * However, the function only logs to the console if you have set the log level + * to OT.LOG or OT.DEBUG, + * by calling OT.setLogLevel(OT.LOG) or OT.setLogLevel(OT.DEBUG). + * + * @param {String} message The string to log. + * + * @name OT.log + * @memberof OT + * @function + * @see OT.setLogLevel() + */ +// tb_require('../../../helpers/helpers.js') -/** -* Removes an event handler. -* -*

Pass in an event name and a handler method, the handler is removed for that event:

-* -*
OT.off("exceptionEvent", exceptionEventHandler);
-* -*

If you pass in an event name and no handler method, all handlers are removed for that -* events:

-* -*
OT.off("exceptionEvent");
-* -*

-* The method also supports an alternate syntax, in which the first parameter is an object that is a -* hash map of -* event names and handler functions and the second parameter (optional) is the context for matching -* handlers: -*

-*
-* OT.off(
-*   {
-*     exceptionEvent: exceptionEventHandler
-*   },
-*   this
-* );
-* 
-* -* @param {String} type (Optional) The string identifying the type of event. You can use a space to -* specify multiple events, as in "eventName1 eventName2 eventName3". If you pass in no -* type value (or other arguments), all event handlers are removed for the object. -* @param {Function} handler (Optional) The event handler function to remove. If you pass in no -* handler, all event handlers are removed for the specified event type. -* @param {Object} context (Optional) If you specify a context, the event handler is -* removed for all specified events and handlers that use the specified context. -* -* @memberof OT -* @method off -* @see on() -* @see once() -* @see Events -*/ +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ -/** - * Dispatched by the OT class when the app encounters an exception. - * Note that you set up an event handler for the exception event by calling the - * OT.on() method. - * - * @name exception - * @event - * @borrows ExceptionEvent#message as this.message - * @memberof OT - * @see ExceptionEvent - */ +// Rumor Messaging for JS +// +// https://tbwiki.tokbox.com/index.php/Rumor_:_Messaging_FrameWork +// +// @todo Rumor { +// Add error codes for all the error cases +// Add Dependability commands +// } - if (!window.OT) window.OT = OT; - if (!window.TB) window.TB = OT; +OT.Rumor = { + MessageType: { + // This is used to subscribe to address/addresses. The address/addresses the + // client specifies here is registered on the server. Once any message is sent to + // that address/addresses, the client receives that message. + SUBSCRIBE: 0, + + // This is used to unsubscribe to address / addresses. Once the client unsubscribe + // to an address, it will stop getting messages sent to that address. + UNSUBSCRIBE: 1, + + // This is used to send messages to arbitrary address/ addresses. Messages can be + // anything and Rumor will not care about what is included. + MESSAGE: 2, + + // This will be the first message that the client sends to the server. It includes + // the uniqueId for that client connection and a disconnect_notify address that will + // be notified once the client disconnects. + CONNECT: 3, + + // This will be the message used by the server to notify an address that a + // client disconnected. + DISCONNECT: 4, + + //Enhancements to support Keepalives + PING: 7, + PONG: 8, + STATUS: 9 + } +}; + +// tb_require('../../../helpers/helpers.js') +// tb_require('./rumor.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT, OTPlugin */ -})(window); !(function() { - OT.Collection = function(idField) { - var _models = [], - _byId = {}, - _idField = idField || 'id'; + OT.Rumor.PluginSocket = function(messagingURL, events) { - OT.$.eventing(this, true); + var webSocket, + state = 'initializing'; - var modelProperty = function(model, property) { - if(OT.$.isFunction(model[property])) { - return model[property](); + OTPlugin.initRumorSocket(messagingURL, OT.$.bind(function(err, rumorSocket) { + if(err) { + state = 'closed'; + events.onClose({ code: 4999 }); + } else if(state === 'initializing') { + webSocket = rumorSocket; + + webSocket.onOpen(function() { + state = 'open'; + events.onOpen(); + }); + webSocket.onClose(function(error) { + state = 'closed'; /* CLOSED */ + events.onClose({ code: error }); + }); + webSocket.onError(function(error) { + state = 'closed'; /* CLOSED */ + events.onError(error); + /* native websockets seem to do this, so should we */ + events.onClose({ code: error }); + }); + + webSocket.onMessage(function(type, addresses, headers, payload) { + var msg = new OT.Rumor.Message(type, addresses, headers, payload); + events.onMessage(msg); + }); + + webSocket.open(); } else { - return model[property]; + this.close(); + } + }, this)); + + this.close = function() { + if(state === 'initializing' || state === 'closed') { + state = 'closed'; + return; + } + + webSocket.close(1000, ''); + }; + + this.send = function(msg) { + if(state === 'open') { + webSocket.send(msg); } }; - var onModelUpdate = OT.$.bind(function onModelUpdate (event) { - this.trigger('update', event); - this.trigger('update:'+event.target.id, event); - }, this), - - onModelDestroy = OT.$.bind(function onModelDestroyed (event) { - this.remove(event.target, event.reason); - }, this); - - - this.reset = function() { - // Stop listening on the models, they are no longer our problem - OT.$.forEach(_models, function(model) { - model.off('updated', onModelUpdate, this); - model.off('destroyed', onModelDestroy, this); - }, this); - - _models = []; - _byId = {}; + this.isClosed = function() { + return state === 'closed'; }; - this.destroy = function(reason) { - OT.$.forEach(_models, function(model) { - if(model && typeof model.destroy === 'function') { - model.destroy(reason, true); - } - }); - - this.reset(); - this.off(); - }; - - this.get = function(id) { return id && _byId[id] !== void 0 ? _models[_byId[id]] : void 0; }; - this.has = function(id) { return id && _byId[id] !== void 0; }; - - this.toString = function() { return _models.toString(); }; - - // Return only models filtered by either a dict of properties - // or a filter function. - // - // @example Return all publishers with a streamId of 1 - // OT.publishers.where({streamId: 1}) - // - // @example The same thing but filtering using a filter function - // OT.publishers.where(function(publisher) { - // return publisher.stream.id === 4; - // }); - // - // @example The same thing but filtering using a filter function - // executed with a specific this - // OT.publishers.where(function(publisher) { - // return publisher.stream.id === 4; - // }, self); - // - this.where = function(attrsOrFilterFn, context) { - if (OT.$.isFunction(attrsOrFilterFn)) return OT.$.filter(_models, attrsOrFilterFn, context); - - return OT.$.filter(_models, function(model) { - for (var key in attrsOrFilterFn) { - if(!attrsOrFilterFn.hasOwnProperty(key)) { - continue; - } - if (modelProperty(model, key) !== attrsOrFilterFn[key]) return false; - } - - return true; - }); - }; - - // Similar to where in behaviour, except that it only returns - // the first match. - this.find = function(attrsOrFilterFn, context) { - var filterFn; - - if (OT.$.isFunction(attrsOrFilterFn)) { - filterFn = attrsOrFilterFn; - } - else { - filterFn = function(model) { - for (var key in attrsOrFilterFn) { - if(!attrsOrFilterFn.hasOwnProperty(key)) { - continue; - } - if (modelProperty(model, key) !== attrsOrFilterFn[key]) return false; - } - - return true; - }; - } - - filterFn = OT.$.bind(filterFn, context); - - for (var i=0; i<_models.length; ++i) { - if (filterFn(_models[i]) === true) return _models[i]; - } - - return null; - }; - - this.add = function(model) { - var id = modelProperty(model, _idField); - - if (this.has(id)) { - OT.warn('Model ' + id + ' is already in the collection', _models); - return this; - } - - _byId[id] = _models.push(model) - 1; - - model.on('updated', onModelUpdate, this); - model.on('destroyed', onModelDestroy, this); - - this.trigger('add', model); - this.trigger('add:'+id, model); - - return this; - }; - - this.remove = function(model, reason) { - var id = modelProperty(model, _idField); - - _models.splice(_byId[id], 1); - - // Shuffle everyone down one - for (var i=_byId[id]; i<_models.length; ++i) { - _byId[_models[i][_idField]] = i; - } - - delete _byId[id]; - - model.off('updated', onModelUpdate, this); - model.off('destroyed', onModelDestroy, this); - - this.trigger('remove', model, reason); - this.trigger('remove:'+id, model, reason); - - return this; - }; - - // Used by session connecto fire add events after adding listeners - this._triggerAddEvents = function() { - var models = this.where.apply(this, arguments); - OT.$.forEach(models, function(model) { - this.trigger('add', model); - this.trigger('add:' + modelProperty(model, _idField), model); - }, this); - }; - - this.length = function() { - return _models.length; - }; }; }(this)); -!(function() { - /** - * The Event object defines the basic OpenTok event object that is passed to - * event listeners. Other OpenTok event classes implement the properties and methods of - * the Event object.

- * - *

For example, the Stream object dispatches a streamPropertyChanged event when - * the stream's properties are updated. You add a callback for an event using the - * on() method of the Stream object:

- * - *
-   * stream.on("streamPropertyChanged", function (event) {
-   *     alert("Properties changed for stream " + event.target.streamId);
-   * });
- * - * @class Event - * @property {Boolean} cancelable Whether the event has a default behavior that is cancelable - * (true) or not (false). You can cancel the default behavior by - * calling the preventDefault() method of the Event object in the callback - * function. (See preventDefault().) - * - * @property {Object} target The object that dispatched the event. - * - * @property {String} type The type of event. - */ - OT.Event = OT.$.eventing.Event(); - /** - * Prevents the default behavior associated with the event from taking place. - * - *

To see whether an event has a default behavior, check the cancelable property - * of the event object.

- * - *

Call the preventDefault() method in the callback function for the event.

- * - *

The following events have default behaviors:

- * - * - * - * @method #preventDefault - * @memberof Event - */ - /** - * Whether the default event behavior has been prevented via a call to - * preventDefault() (true) or not (false). - * See preventDefault(). - * @method #isDefaultPrevented - * @return {Boolean} - * @memberof Event - */ +// tb_require('../../../helpers/helpers.js') - // Event names lookup - OT.Event.names = { - // Activity Status for cams/mics - ACTIVE: 'active', - INACTIVE: 'inactive', - UNKNOWN: 'unknown', - - // Archive types - PER_SESSION: 'perSession', - PER_STREAM: 'perStream', - - // OT Events - EXCEPTION: 'exception', - ISSUE_REPORTED: 'issueReported', - - // Session Events - SESSION_CONNECTED: 'sessionConnected', - SESSION_DISCONNECTED: 'sessionDisconnected', - STREAM_CREATED: 'streamCreated', - STREAM_DESTROYED: 'streamDestroyed', - CONNECTION_CREATED: 'connectionCreated', - CONNECTION_DESTROYED: 'connectionDestroyed', - SIGNAL: 'signal', - STREAM_PROPERTY_CHANGED: 'streamPropertyChanged', - MICROPHONE_LEVEL_CHANGED: 'microphoneLevelChanged', - - - // Publisher Events - RESIZE: 'resize', - SETTINGS_BUTTON_CLICK: 'settingsButtonClick', - DEVICE_INACTIVE: 'deviceInactive', - INVALID_DEVICE_NAME: 'invalidDeviceName', - ACCESS_ALLOWED: 'accessAllowed', - ACCESS_DENIED: 'accessDenied', - ACCESS_DIALOG_OPENED: 'accessDialogOpened', - ACCESS_DIALOG_CLOSED: 'accessDialogClosed', - ECHO_CANCELLATION_MODE_CHANGED: 'echoCancellationModeChanged', - PUBLISHER_DESTROYED: 'destroyed', - - // Subscriber Events - SUBSCRIBER_DESTROYED: 'destroyed', - - // DeviceManager Events - DEVICES_DETECTED: 'devicesDetected', - - // DevicePanel Events - DEVICES_SELECTED: 'devicesSelected', - CLOSE_BUTTON_CLICK: 'closeButtonClick', - - MICLEVEL : 'microphoneActivityLevel', - MICGAINCHANGED : 'microphoneGainChanged', - - // Environment Loader - ENV_LOADED: 'envLoaded', - ENV_UNLOADED: 'envUnloaded', - - // Audio activity Events - AUDIO_LEVEL_UPDATED: 'audioLevelUpdated' - }; - - OT.ExceptionCodes = { - JS_EXCEPTION: 2000, - AUTHENTICATION_ERROR: 1004, - INVALID_SESSION_ID: 1005, - CONNECT_FAILED: 1006, - CONNECT_REJECTED: 1007, - CONNECTION_TIMEOUT: 1008, - NOT_CONNECTED: 1010, - P2P_CONNECTION_FAILED: 1013, - API_RESPONSE_FAILURE: 1014, - UNABLE_TO_PUBLISH: 1500, - UNABLE_TO_SUBSCRIBE: 1501, - UNABLE_TO_FORCE_DISCONNECT: 1520, - UNABLE_TO_FORCE_UNPUBLISH: 1530 - }; - - /** - * The {@link OT} class dispatches exception events when the OpenTok API encounters - * an exception (error). The ExceptionEvent object defines the properties of the event - * object that is dispatched. - * - *

Note that you set up a callback for the exception event by calling the - * OT.on() method.

- * - * @class ExceptionEvent - * @property {Number} code The error code. The following is a list of error codes:

- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
- * code - * - * - * title - *
- * 1004 - * - * - * Authentication error - *
- * 1005 - * - * - * Invalid Session ID - *
- * 1006 - * - * - * Connect Failed - *
- * 1007 - * - * - * Connect Rejected - *
- * 1008 - * - * - * Connect Time-out - *
- * 1009 - * - * - * Security Error - *
- * 1010 - * - * - * Not Connected - *
- * 1011 - * - * - * Invalid Parameter - *
- * 1013 - * - * Connection Failed - *
- * 1014 - * - * API Response Failure - *
- * 1500 - * - * Unable to Publish - *
- * 1520 - * - * Unable to Force Disconnect - *
- * 1530 - * - * Unable to Force Unpublish - *
- * 1535 - * - * Force Unpublish on Invalid Stream - *
- * 2000 - * - * - * Internal Error - *
- * 2010 - * - * - * Report Issue Failure - *
- * - *

Check the message property for more details about the error.

- * - * @property {String} message The error message. - * - * @property {Object} target The object that the event pertains to. For an - * exception event, this will be an object other than the OT object - * (such as a Session object or a Publisher object). - * - * @property {String} title The error title. - * @augments Event - */ - OT.ExceptionEvent = function (type, message, title, code, component, target) { - OT.Event.call(this, type); - - this.message = message; - this.title = title; - this.code = code; - this.component = component; - this.target = target; - }; - - - OT.IssueReportedEvent = function (type, issueId) { - OT.Event.call(this, type); - this.issueId = issueId; - }; - - // Triggered when the JS dynamic config and the DOM have loaded. - OT.EnvLoadedEvent = function (type) { - OT.Event.call(this, type); - }; - - -/** - * Dispatched by the Session object when a client connects to or disconnects from a {@link Session}. - * For the local client, the Session object dispatches a "sessionConnected" or "sessionDisconnected" - * event, defined by the {@link SessionConnectEvent} and {@link SessionDisconnectEvent} classes. - * - *
Example
- * - *

The following code keeps a running total of the number of connections to a session - * by monitoring the connections property of the sessionConnect, - * connectionCreated and connectionDestroyed events:

- * - *
var apiKey = ""; // Replace with your API key. See https://dashboard.tokbox.com/projects
- * var sessionID = ""; // Replace with your own session ID.
- *                     // See https://dashboard.tokbox.com/projects
- * var token = ""; // Replace with a generated token that has been assigned the moderator role.
- *                 // See https://dashboard.tokbox.com/projects
- * var connectionCount = 0;
- *
- * var session = OT.initSession(apiKey, sessionID);
- * session.on("connectionCreated", function(event) {
- *    connectionCount++;
- *    displayConnectionCount();
- * });
- * session.on("connectionDestroyed", function(event) {
- *    connectionCount--;
- *    displayConnectionCount();
- * });
- * session.connect(token);
- *
- * function displayConnectionCount() {
- *     document.getElementById("connectionCountField").value = connectionCount.toString();
- * }
- * - *

This example assumes that there is an input text field in the HTML DOM - * with the id set to "connectionCountField":

- * - *
<input type="text" id="connectionCountField" value="0"></input>
- * - * - * @property {Connection} connection A Connection objects for the connections that was - * created or deleted. - * - * @property {Array} connections Deprecated. Use the connection property. A - * connectionCreated or connectionDestroyed event is dispatched - * for each connection created and destroyed in the session. - * - * @property {String} reason For a connectionDestroyed event, - * a description of why the connection ended. This property can have two values: - *

- *
    - *
  • "clientDisconnected" — A client disconnected from the session by calling - * the disconnect() method of the Session object or by closing the browser. - * (See Session.disconnect().)
  • - * - *
  • "forceDisconnected" — A moderator has disconnected the publisher - * from the session, by calling the forceDisconnect() method of the Session - * object. (See Session.forceDisconnect().)
  • - * - *
  • "networkDisconnected" — The network connection terminated abruptly - * (for example, the client lost their internet connection).
  • - *
- * - *

Depending on the context, this description may allow the developer to refine - * the course of action they take in response to an event.

- * - *

For a connectionCreated event, this string is undefined.

- * - * @class ConnectionEvent - * @augments Event - */ - var connectionEventPluralDeprecationWarningShown = false; - OT.ConnectionEvent = function (type, connection, reason) { - OT.Event.call(this, type, false); - - if (OT.$.canDefineProperty) { - Object.defineProperty(this, 'connections', { - get: function() { - if(!connectionEventPluralDeprecationWarningShown) { - OT.warn('OT.ConnectionEvent connections property is deprecated, ' + - 'use connection instead.'); - connectionEventPluralDeprecationWarningShown = true; - } - return [connection]; - } - }); - } else { - this.connections = [connection]; - } - - this.connection = connection; - this.reason = reason; - }; - -/** - * StreamEvent is an event that can have the type "streamCreated" or "streamDestroyed". - * These events are dispatched by the Session object when another client starts or - * stops publishing a stream to a {@link Session}. For a local client's stream, the - * Publisher object dispatches the event. - * - *

Example — streamCreated event dispatched - * by the Session object

- *

The following code initializes a session and sets up an event listener for when - * a stream published by another client is created:

- * - *
- * session.on("streamCreated", function(event) {
- *   // streamContainer is a DOM element
- *   subscriber = session.subscribe(event.stream, targetElement);
- * }).connect(token);
- * 
- * - *

Example — streamDestroyed event dispatched - * by the Session object

- * - *

The following code initializes a session and sets up an event listener for when - * other clients' streams end:

- * - *
- * session.on("streamDestroyed", function(event) {
- *     console.log("Stream " + event.stream.name + " ended. " + event.reason);
- * }).connect(token);
- * 
- * - *

Example — streamCreated event dispatched - * by a Publisher object

- *

The following code publishes a stream and adds an event listener for when the streaming - * starts

- * - *
- * var publisher = session.publish(targetElement)
- *   .on("streamCreated", function(event) {
- *     console.log("Publisher started streaming.");
- *   );
- * 
- * - *

Example — streamDestroyed event - * dispatched by a Publisher object

- * - *

The following code publishes a stream, and leaves the Publisher in the HTML DOM - * when the streaming stops:

- * - *
- * var publisher = session.publish(targetElement)
- *   .on("streamDestroyed", function(event) {
- *     event.preventDefault();
- *     console.log("Publisher stopped streaming.");
- *   );
- * 
- * - * @class StreamEvent - * - * @property {Boolean} cancelable Whether the event has a default behavior that is cancelable - * (true) or not (false). You can cancel the default behavior by calling - * the preventDefault() method of the StreamEvent object in the event listener - * function. The streamDestroyed - * event is cancelable. (See preventDefault().) - * - * @property {String} reason For a streamDestroyed event, - * a description of why the session disconnected. This property can have one of the following - * values: - *

- *
    - *
  • "clientDisconnected" — A client disconnected from the session by calling - * the disconnect() method of the Session object or by closing the browser. - * (See Session.disconnect().)
  • - * - *
  • "forceDisconnected" — A moderator has disconnected the publisher of the - * stream from the session, by calling the forceDisconnect() method of the Session -* object. (See Session.forceDisconnect().)
  • - * - *
  • "forceUnpublished" — A moderator has forced the publisher of the stream - * to stop publishing the stream, by calling the forceUnpublish() method of the - * Session object. (See Session.forceUnpublish().)
  • - * - *
  • "networkDisconnected" — The network connection terminated abruptly (for - * example, the client lost their internet connection).
  • - * - *
- * - *

Depending on the context, this description may allow the developer to refine - * the course of action they take in response to an event.

- * - *

For a streamCreated event, this string is undefined.

- * - * @property {Stream} stream A Stream object corresponding to the stream that was added (in the - * case of a streamCreated event) or deleted (in the case of a - * streamDestroyed event). - * - * @property {Array} streams Deprecated. Use the stream property. A - * streamCreated or streamDestroyed event is dispatched for - * each stream added or destroyed. - * - * @augments Event - */ - - var streamEventPluralDeprecationWarningShown = false; - OT.StreamEvent = function (type, stream, reason, cancelable) { - OT.Event.call(this, type, cancelable); - - if (OT.$.canDefineProperty) { - Object.defineProperty(this, 'streams', { - get: function() { - if(!streamEventPluralDeprecationWarningShown) { - OT.warn('OT.StreamEvent streams property is deprecated, use stream instead.'); - streamEventPluralDeprecationWarningShown = true; - } - return [stream]; - } - }); - } else { - this.streams = [stream]; - } - - this.stream = stream; - this.reason = reason; - }; - -/** -* Prevents the default behavior associated with the event from taking place. -* -*

For the streamDestroyed event dispatched by the Session object, -* the default behavior is that all Subscriber objects that are subscribed to the stream are -* unsubscribed and removed from the HTML DOM. Each Subscriber object dispatches a -* destroyed event when the element is removed from the HTML DOM. If you call the -* preventDefault() method in the event listener for the streamDestroyed -* event, the default behavior is prevented and you can clean up Subscriber objects using your -* own code. See -* Session.getSubscribersForStream().

-*

-* For the streamDestroyed event dispatched by a Publisher object, the default -* behavior is that the Publisher object is removed from the HTML DOM. The Publisher object -* dispatches a destroyed event when the element is removed from the HTML DOM. -* If you call the preventDefault() method in the event listener for the -* streamDestroyed event, the default behavior is prevented, and you can -* retain the Publisher for reuse or clean it up using your own code. -*

-*

To see whether an event has a default behavior, check the cancelable property of -* the event object.

-* -*

Call the preventDefault() method in the event listener function for the event.

-* -* @method #preventDefault -* @memberof StreamEvent -*/ - -/** - * The Session object dispatches SessionConnectEvent object when a session has successfully - * connected in response to a call to the connect() method of the Session object. - *

- * In version 2.2, the completionHandler of the Session.connect() method - * indicates success or failure in connecting to the session. - * - * @class SessionConnectEvent - * @property {Array} connections Deprecated in version 2.2 (and set to an empty array). In - * version 2.2, listen for the connectionCreated event dispatched by the Session - * object. In version 2.2, the Session object dispatches a connectionCreated event - * for each connection (including your own). This includes connections present when you first - * connect to the session. - * - * @property {Array} streams Deprecated in version 2.2 (and set to an empty array). In version - * 2.2, listen for the streamCreated event dispatched by the Session object. In - * version 2.2, the Session object dispatches a streamCreated event for each stream - * other than those published by your client. This includes streams - * present when you first connect to the session. - * - * @see Session.connect()

- * @augments Event - */ - - var sessionConnectedConnectionsDeprecationWarningShown = false; - var sessionConnectedStreamsDeprecationWarningShown = false; - var sessionConnectedArchivesDeprecationWarningShown = false; - - OT.SessionConnectEvent = function (type) { - OT.Event.call(this, type, false); - if (OT.$.canDefineProperty) { - Object.defineProperties(this, { - connections: { - get: function() { - if(!sessionConnectedConnectionsDeprecationWarningShown) { - OT.warn('OT.SessionConnectedEvent no longer includes connections. Listen ' + - 'for connectionCreated events instead.'); - sessionConnectedConnectionsDeprecationWarningShown = true; - } - return []; - } - }, - streams: { - get: function() { - if(!sessionConnectedStreamsDeprecationWarningShown) { - OT.warn('OT.SessionConnectedEvent no longer includes streams. Listen for ' + - 'streamCreated events instead.'); - sessionConnectedConnectionsDeprecationWarningShown = true; - } - return []; - } - }, - archives: { - get: function() { - if(!sessionConnectedArchivesDeprecationWarningShown) { - OT.warn('OT.SessionConnectedEvent no longer includes archives. Listen for ' + - 'archiveStarted events instead.'); - sessionConnectedArchivesDeprecationWarningShown = true; - } - return []; - } - } - }); - } else { - this.connections = []; - this.streams = []; - this.archives = []; - } - }; - -/** - * The Session object dispatches SessionDisconnectEvent object when a session has disconnected. - * This event may be dispatched asynchronously in response to a successful call to the - * disconnect() method of the session object. - * - *

- * Example - *

- *

- * The following code initializes a session and sets up an event listener for when a session is - * disconnected. - *

- *
var apiKey = ""; // Replace with your API key. See https://dashboard.tokbox.com/projects
- *  var sessionID = ""; // Replace with your own session ID.
- *                      // See https://dashboard.tokbox.com/projects
- *  var token = ""; // Replace with a generated token that has been assigned the moderator role.
- *                  // See https://dashboard.tokbox.com/projects
- *
- *  var session = OT.initSession(apiKey, sessionID);
- *  session.on("sessionDisconnected", function(event) {
- *      alert("The session disconnected. " + event.reason);
- *  });
- *  session.connect(token);
- *  
- * - * @property {String} reason A description of why the session disconnected. - * This property can have two values: - *

- *
    - *
  • "clientDisconnected" — A client disconnected from the session by calling - * the disconnect() method of the Session object or by closing the browser. - * ( See Session.disconnect().)
  • - *
  • "forceDisconnected" — A moderator has disconnected you from the session - * by calling the forceDisconnect() method of the Session object. (See - * Session.forceDisconnect().)
  • - *
  • "networkDisconnected" — The network connection terminated abruptly - * (for example, the client lost their internet connection).
  • - *
- * - * @class SessionDisconnectEvent - * @augments Event - */ - OT.SessionDisconnectEvent = function (type, reason, cancelable) { - OT.Event.call(this, type, cancelable); - this.reason = reason; - }; - -/** -* Prevents the default behavior associated with the event from taking place. -* -*

For the sessionDisconnectEvent, the default behavior is that all Subscriber -* objects are unsubscribed and removed from the HTML DOM. Each Subscriber object dispatches a -* destroyed event when the element is removed from the HTML DOM. If you call the -* preventDefault() method in the event listener for the sessionDisconnect -* event, the default behavior is prevented, and you can, optionally, clean up Subscriber objects -* using your own code). -* -*

To see whether an event has a default behavior, check the cancelable property of -* the event object.

-* -*

Call the preventDefault() method in the event listener function for the event.

-* -* @method #preventDefault -* @memberof SessionDisconnectEvent -*/ - -/** - * The Session object dispatches a streamPropertyChanged event in the - * following circumstances: - * - *
    - * - *
  • When a publisher starts or stops publishing audio or video. This change causes - * the hasAudio or hasVideo property of the Stream object to - * change. This change results from a call to the publishAudio() or - * publishVideo() methods of the Publish object.
  • - * - *
  • When the videoDimensions property of a stream changes. For more information, - * see Stream.videoDimensions.
  • - * - *
- * - * @class StreamPropertyChangedEvent - * @property {String} changedProperty The property of the stream that changed. This value - * is either "hasAudio", "hasVideo", or "videoDimensions". - * @property {Object} newValue The new value of the property (after the change). - * @property {Object} oldValue The old value of the property (before the change). - * @property {Stream} stream The Stream object for which a property has changed. - * - * @see Publisher.publishAudio()

- * @see Publisher.publishVideo()

- * @see Stream.videoDimensions

- * @augments Event - */ - OT.StreamPropertyChangedEvent = function (type, stream, changedProperty, oldValue, newValue) { - OT.Event.call(this, type, false); - this.type = type; - this.stream = stream; - this.changedProperty = changedProperty; - this.oldValue = oldValue; - this.newValue = newValue; - }; - -/** - * Defines event objects for the archiveStarted and archiveStopped events. - * The Session object dispatches these events when an archive recording of the session starts and - * stops. - * - * @property {String} id The archive ID. - * @property {String} name The name of the archive. You can assign an archive a name when you create - * it, using the OpenTok REST API or one of the - * OpenTok server SDKs. - * - * @class ArchiveEvent - * @augments Event - */ - OT.ArchiveEvent = function (type, archive) { - OT.Event.call(this, type, false); - this.type = type; - this.id = archive.id; - this.name = archive.name; - this.status = archive.status; - this.archive = archive; - }; - - OT.ArchiveUpdatedEvent = function (stream, key, oldValue, newValue) { - OT.Event.call(this, 'updated', false); - this.target = stream; - this.changedProperty = key; - this.oldValue = oldValue; - this.newValue = newValue; - }; - -/** - * The Session object dispatches a signal event when the client receives a signal from the session. - * - * @class SignalEvent - * @property {String} type The type assigned to the signal (if there is one). Use the type to - * filter signals received (by adding an event handler for signal:type1 or signal:type2, etc.) - * @property {String} data The data string sent with the signal (if there is one). - * @property {Connection} from The Connection corresponding to the client that sent with the signal. - * - * @see Session.signal()

- * @see Session events (signal and signal:type)

- * @augments Event - */ - OT.SignalEvent = function(type, data, from) { - OT.Event.call(this, type ? 'signal:' + type : OT.Event.names.SIGNAL, false); - this.data = data; - this.from = from; - }; - - OT.StreamUpdatedEvent = function (stream, key, oldValue, newValue) { - OT.Event.call(this, 'updated', false); - this.target = stream; - this.changedProperty = key; - this.oldValue = oldValue; - this.newValue = newValue; - }; - - OT.DestroyedEvent = function(type, target, reason) { - OT.Event.call(this, type, false); - this.target = target; - this.reason = reason; - }; - -/** - * Defines the event object for the videoDisabled and videoEnabled events - * dispatched by the Subscriber. - * - * @class VideoEnabledChangedEvent - * - * @property {Boolean} cancelable Whether the event has a default behavior that is cancelable - * (true) or not (false). You can cancel the default behavior by - * calling the preventDefault() method of the event object in the callback - * function. (See preventDefault().) - * - * @property {String} reason The reason the video was disabled or enabled. This can be set to one of - * the following values: - * - *
    - * - *
  • "publishVideo" — The publisher started or stopped publishing video, - * by calling publishVideo(true) or publishVideo(false).
  • - * - *
  • "quality" — The OpenTok Media Router starts or stops sending video - * to the subscriber based on stream quality changes. This feature of the OpenTok Media - * Router has a subscriber drop the video stream when connectivity degrades. (The subscriber - * continues to receive the audio stream, if there is one.) - *

    - * If connectivity improves to support video again, the Subscriber object dispatches - * a videoEnabled event, and the Subscriber resumes receiving video. - *

    - * By default, the Subscriber displays a video disabled indicator when a - * videoDisabled event with this reason is dispatched and removes the indicator - * when the videoDisabled event with this reason is dispatched. You can control - * the display of this icon by calling the setStyle() method of the Subscriber, - * setting the videoDisabledDisplayMode property(or you can set the style when - * calling the Session.subscribe() method, setting the style property - * of the properties parameter). - *

    - * This feature is only available in sessions that use the OpenTok Media Router (sessions with - * the media mode - * set to routed), not in sessions with the media mode set to relayed. - *

  • - * - *
  • "subscribeToVideo" — The subscriber started or stopped subscribing to - * video, by calling subscribeToVideo(true) or subscribeToVideo(false). - *
  • - * - *
- * - * @property {Object} target The object that dispatched the event. - * - * @property {String} type The type of event: "videoDisabled" or - * "videoEnabled". - * - * @see Subscriber videoDisabled event

- * @see Subscriber videoEnabled event

- * @augments Event - */ - OT.VideoEnabledChangedEvent = function(type, properties) { - OT.Event.call(this, type, false); - this.reason = properties.reason; - }; - - OT.VideoDisableWarningEvent = function(type/*, properties*/) { - OT.Event.call(this, type, false); - }; - -/** - * Dispatched periodically by a Subscriber or Publisher object to indicate the audio - * level. This event is dispatched up to 60 times per second, depending on the browser. - * - * @property {String} audioLevel The audio level, from 0 to 1.0. Adjust this value logarithmically - * for use in adjusting a user interface element, such as a volume meter. Use a moving average - * to smooth the data. - * - * @class AudioLevelUpdatedEvent - * @augments Event - */ - OT.AudioLevelUpdatedEvent = function(audioLevel) { - OT.Event.call(this, OT.Event.names.AUDIO_LEVEL_UPDATED, false); - this.audioLevel = audioLevel; - }; - -})(window); -/* jshint ignore:start */ // https://code.google.com/p/stringencoding/ // An implementation of http://encoding.spec.whatwg.org/#api +// Modified by TokBox to remove all encoding support except for utf-8 /** * @license Copyright 2014 Joshua Bell @@ -9006,21 +6199,23 @@ waitForDomReady(); * * Original source: https://github.com/inexorabletash/text-encoding ***/ +/*jshint unused:false*/ (function(global) { 'use strict'; - var browser = OT.$.browserVersion(); - if(browser && browser.browser === 'IE' && browser.version < 10) { + + if(OT.$.env && OT.$.env.name === 'IE' && OT.$.env.version < 10) { return; // IE 8 doesn't do websockets. No websockets, no encoding. } if ( (global.TextEncoder !== void 0) && (global.TextDecoder !== void 0)) { // defer to the native ones - // @todo is this a good idea? return; } + /* jshint ignore:start */ + // // Utilities // @@ -9058,8 +6253,10 @@ waitForDomReady(); // 4. Encodings // - /** @const */ var EOF_byte = -1; - /** @const */ var EOF_code_point = -1; + /** @const */ + var EOF_byte = -1; + /** @const */ + var EOF_code_point = -1; /** * @constructor @@ -9690,52 +6887,6 @@ waitForDomReady(); /** @type {Object.|Array.>)>} */ var indexes = global['encoding-indexes'] || {}; - /** - * @param {number} pointer The |pointer| to search for in the gb18030 index. - * @return {?number} The code point corresponding to |pointer| in |index|, - * or null if |code point| is not in the gb18030 index. - */ - function indexGB18030CodePointFor(pointer) { - if ((pointer > 39419 && pointer < 189000) || (pointer > 1237575)) { - return null; - } - var /** @type {number} */ offset = 0, - /** @type {number} */ code_point_offset = 0, - /** @type {Array.>} */ index = indexes['gb18030']; - var i; - for (i = 0; i < index.length; ++i) { - var entry = index[i]; - if (entry[0] <= pointer) { - offset = entry[0]; - code_point_offset = entry[1]; - } else { - break; - } - } - return code_point_offset + pointer - offset; - } - - /** - * @param {number} code_point The |code point| to locate in the gb18030 index. - * @return {number} The first pointer corresponding to |code point| in the - * gb18030 index. - */ - function indexGB18030PointerFor(code_point) { - var /** @type {number} */ offset = 0, - /** @type {number} */ pointer_offset = 0, - /** @type {Array.>} */ index = indexes['gb18030']; - var i; - for (i = 0; i < index.length; ++i) { - var entry = index[i]; - if (entry[1] <= code_point) { - offset = entry[1]; - pointer_offset = entry[0]; - } else { - break; - } - } - return pointer_offset + code_point - offset; - } // // 7. The encoding @@ -9749,10 +6900,10 @@ waitForDomReady(); */ function UTF8Decoder(options) { var fatal = options.fatal; - var /** @type {number} */ utf8_code_point = 0, - /** @type {number} */ utf8_bytes_needed = 0, - /** @type {number} */ utf8_bytes_seen = 0, - /** @type {number} */ utf8_lower_boundary = 0; + var utf8_code_point = 0, + utf8_bytes_needed = 0, + utf8_bytes_seen = 0, + utf8_lower_boundary = 0; /** * @param {ByteInputStream} byte_pointer The byte stream to decode. @@ -9871,1394 +7022,6 @@ waitForDomReady(); return new UTF8Decoder(options); }; - // - // 8. Legacy single-byte encodings - // - - /** - * @constructor - * @param {Array.} index The encoding index. - * @param {{fatal: boolean}} options - */ - function SingleByteDecoder(index, options) { - var fatal = options.fatal; - /** - * @param {ByteInputStream} byte_pointer The byte stream to decode. - * @return {?number} The next code point decoded, or null if not enough - * data exists in the input stream to decode a complete code point. - */ - this.decode = function(byte_pointer) { - var bite = byte_pointer.get(); - if (bite === EOF_byte) { - return EOF_code_point; - } - byte_pointer.offset(1); - if (inRange(bite, 0x00, 0x7F)) { - return bite; - } - var code_point = index[bite - 0x80]; - if (code_point === null) { - return decoderError(fatal); - } - return code_point; - }; - } - - /** - * @constructor - * @param {Array.} index The encoding index. - * @param {{fatal: boolean}} options - */ - function SingleByteEncoder(index, options) { - var fatal = options.fatal; - /** - * @param {ByteOutputStream} output_byte_stream Output byte stream. - * @param {CodePointInputStream} code_point_pointer Input stream. - * @return {number} The last byte emitted. - */ - this.encode = function(output_byte_stream, code_point_pointer) { - var code_point = code_point_pointer.get(); - if (code_point === EOF_code_point) { - return EOF_byte; - } - code_point_pointer.offset(1); - if (inRange(code_point, 0x0000, 0x007F)) { - return output_byte_stream.emit(code_point); - } - var pointer = indexPointerFor(code_point, index); - if (pointer === null) { - encoderError(code_point); - } - return output_byte_stream.emit(pointer + 0x80); - }; - } - - (function() { - ['ibm864', 'ibm866', 'iso-8859-2', 'iso-8859-3', 'iso-8859-4', - 'iso-8859-5', 'iso-8859-6', 'iso-8859-7', 'iso-8859-8', 'iso-8859-10', - 'iso-8859-13', 'iso-8859-14', 'iso-8859-15', 'iso-8859-16', 'koi8-r', - 'koi8-u', 'macintosh', 'windows-874', 'windows-1250', 'windows-1251', - 'windows-1252', 'windows-1253', 'windows-1254', 'windows-1255', - 'windows-1256', 'windows-1257', 'windows-1258', 'x-mac-cyrillic' - ].forEach(function(name) { - var encoding = name_to_encoding[name]; - var index = indexes[name]; - encoding.getDecoder = function(options) { - return new SingleByteDecoder(index, options); - }; - encoding.getEncoder = function(options) { - return new SingleByteEncoder(index, options); - }; - }); - }()); - - // - // 9. Legacy multi-byte Chinese (simplified) encodings - // - - // 9.1 gbk - - /** - * @constructor - * @param {boolean} gb18030 True if decoding gb18030, false otherwise. - * @param {{fatal: boolean}} options - */ - function GBKDecoder(gb18030, options) { - var fatal = options.fatal; - var /** @type {number} */ gbk_first = 0x00, - /** @type {number} */ gbk_second = 0x00, - /** @type {number} */ gbk_third = 0x00; - /** - * @param {ByteInputStream} byte_pointer The byte stream to decode. - * @return {?number} The next code point decoded, or null if not enough - * data exists in the input stream to decode a complete code point. - */ - this.decode = function(byte_pointer) { - var bite = byte_pointer.get(); - if (bite === EOF_byte && gbk_first === 0x00 && - gbk_second === 0x00 && gbk_third === 0x00) { - return EOF_code_point; - } - if (bite === EOF_byte && - (gbk_first !== 0x00 || gbk_second !== 0x00 || gbk_third !== 0x00)) { - gbk_first = 0x00; - gbk_second = 0x00; - gbk_third = 0x00; - decoderError(fatal); - } - byte_pointer.offset(1); - var code_point; - if (gbk_third !== 0x00) { - code_point = null; - if (inRange(bite, 0x30, 0x39)) { - code_point = indexGB18030CodePointFor( - (((gbk_first - 0x81) * 10 + (gbk_second - 0x30)) * 126 + - (gbk_third - 0x81)) * 10 + bite - 0x30); - } - gbk_first = 0x00; - gbk_second = 0x00; - gbk_third = 0x00; - if (code_point === null) { - byte_pointer.offset(-3); - return decoderError(fatal); - } - return code_point; - } - if (gbk_second !== 0x00) { - if (inRange(bite, 0x81, 0xFE)) { - gbk_third = bite; - return null; - } - byte_pointer.offset(-2); - gbk_first = 0x00; - gbk_second = 0x00; - return decoderError(fatal); - } - if (gbk_first !== 0x00) { - if (inRange(bite, 0x30, 0x39) && gb18030) { - gbk_second = bite; - return null; - } - var lead = gbk_first; - var pointer = null; - gbk_first = 0x00; - var offset = bite < 0x7F ? 0x40 : 0x41; - if (inRange(bite, 0x40, 0x7E) || inRange(bite, 0x80, 0xFE)) { - pointer = (lead - 0x81) * 190 + (bite - offset); - } - code_point = pointer === null ? null : - indexCodePointFor(pointer, indexes['gbk']); - if (pointer === null) { - byte_pointer.offset(-1); - } - if (code_point === null) { - return decoderError(fatal); - } - return code_point; - } - if (inRange(bite, 0x00, 0x7F)) { - return bite; - } - if (bite === 0x80) { - return 0x20AC; - } - if (inRange(bite, 0x81, 0xFE)) { - gbk_first = bite; - return null; - } - return decoderError(fatal); - }; - } - - /** - * @constructor - * @param {boolean} gb18030 True if decoding gb18030, false otherwise. - * @param {{fatal: boolean}} options - */ - function GBKEncoder(gb18030, options) { - var fatal = options.fatal; - /** - * @param {ByteOutputStream} output_byte_stream Output byte stream. - * @param {CodePointInputStream} code_point_pointer Input stream. - * @return {number} The last byte emitted. - */ - this.encode = function(output_byte_stream, code_point_pointer) { - var code_point = code_point_pointer.get(); - if (code_point === EOF_code_point) { - return EOF_byte; - } - code_point_pointer.offset(1); - if (inRange(code_point, 0x0000, 0x007F)) { - return output_byte_stream.emit(code_point); - } - var pointer = indexPointerFor(code_point, indexes['gbk']); - if (pointer !== null) { - var lead = div(pointer, 190) + 0x81; - var trail = pointer % 190; - var offset = trail < 0x3F ? 0x40 : 0x41; - return output_byte_stream.emit(lead, trail + offset); - } - if (pointer === null && !gb18030) { - return encoderError(code_point); - } - pointer = indexGB18030PointerFor(code_point); - var byte1 = div(div(div(pointer, 10), 126), 10); - pointer = pointer - byte1 * 10 * 126 * 10; - var byte2 = div(div(pointer, 10), 126); - pointer = pointer - byte2 * 10 * 126; - var byte3 = div(pointer, 10); - var byte4 = pointer - byte3 * 10; - return output_byte_stream.emit(byte1 + 0x81, - byte2 + 0x30, - byte3 + 0x81, - byte4 + 0x30); - }; - } - - name_to_encoding['gbk'].getEncoder = function(options) { - return new GBKEncoder(false, options); - }; - name_to_encoding['gbk'].getDecoder = function(options) { - return new GBKDecoder(false, options); - }; - - // 9.2 gb18030 - name_to_encoding['gb18030'].getEncoder = function(options) { - return new GBKEncoder(true, options); - }; - name_to_encoding['gb18030'].getDecoder = function(options) { - return new GBKDecoder(true, options); - }; - - // 9.3 hz-gb-2312 - - /** - * @constructor - * @param {{fatal: boolean}} options - */ - function HZGB2312Decoder(options) { - var fatal = options.fatal; - var /** @type {boolean} */ hzgb2312 = false, - /** @type {number} */ hzgb2312_lead = 0x00; - /** - * @param {ByteInputStream} byte_pointer The byte stream to decode. - * @return {?number} The next code point decoded, or null if not enough - * data exists in the input stream to decode a complete code point. - */ - this.decode = function(byte_pointer) { - var bite = byte_pointer.get(); - if (bite === EOF_byte && hzgb2312_lead === 0x00) { - return EOF_code_point; - } - if (bite === EOF_byte && hzgb2312_lead !== 0x00) { - hzgb2312_lead = 0x00; - return decoderError(fatal); - } - byte_pointer.offset(1); - if (hzgb2312_lead === 0x7E) { - hzgb2312_lead = 0x00; - if (bite === 0x7B) { - hzgb2312 = true; - return null; - } - if (bite === 0x7D) { - hzgb2312 = false; - return null; - } - if (bite === 0x7E) { - return 0x007E; - } - if (bite === 0x0A) { - return null; - } - byte_pointer.offset(-1); - return decoderError(fatal); - } - if (hzgb2312_lead !== 0x00) { - var lead = hzgb2312_lead; - hzgb2312_lead = 0x00; - var code_point = null; - if (inRange(bite, 0x21, 0x7E)) { - code_point = indexCodePointFor((lead - 1) * 190 + - (bite + 0x3F), indexes['gbk']); - } - if (bite === 0x0A) { - hzgb2312 = false; - } - if (code_point === null) { - return decoderError(fatal); - } - return code_point; - } - if (bite === 0x7E) { - hzgb2312_lead = 0x7E; - return null; - } - if (hzgb2312) { - if (inRange(bite, 0x20, 0x7F)) { - hzgb2312_lead = bite; - return null; - } - if (bite === 0x0A) { - hzgb2312 = false; - } - return decoderError(fatal); - } - if (inRange(bite, 0x00, 0x7F)) { - return bite; - } - return decoderError(fatal); - }; - } - - /** - * @constructor - * @param {{fatal: boolean}} options - */ - function HZGB2312Encoder(options) { - var fatal = options.fatal; - var hzgb2312 = false; - /** - * @param {ByteOutputStream} output_byte_stream Output byte stream. - * @param {CodePointInputStream} code_point_pointer Input stream. - * @return {number} The last byte emitted. - */ - this.encode = function(output_byte_stream, code_point_pointer) { - var code_point = code_point_pointer.get(); - if (code_point === EOF_code_point) { - return EOF_byte; - } - code_point_pointer.offset(1); - if (inRange(code_point, 0x0000, 0x007F) && hzgb2312) { - code_point_pointer.offset(-1); - hzgb2312 = false; - return output_byte_stream.emit(0x7E, 0x7D); - } - if (code_point === 0x007E) { - return output_byte_stream.emit(0x7E, 0x7E); - } - if (inRange(code_point, 0x0000, 0x007F)) { - return output_byte_stream.emit(code_point); - } - if (!hzgb2312) { - code_point_pointer.offset(-1); - hzgb2312 = true; - return output_byte_stream.emit(0x7E, 0x7B); - } - var pointer = indexPointerFor(code_point, indexes['gbk']); - if (pointer === null) { - return encoderError(code_point); - } - var lead = div(pointer, 190) + 1; - var trail = pointer % 190 - 0x3F; - if (!inRange(lead, 0x21, 0x7E) || !inRange(trail, 0x21, 0x7E)) { - return encoderError(code_point); - } - return output_byte_stream.emit(lead, trail); - }; - } - - name_to_encoding['hz-gb-2312'].getEncoder = function(options) { - return new HZGB2312Encoder(options); - }; - name_to_encoding['hz-gb-2312'].getDecoder = function(options) { - return new HZGB2312Decoder(options); - }; - - // - // 10. Legacy multi-byte Chinese (traditional) encodings - // - - // 10.1 big5 - - /** - * @constructor - * @param {{fatal: boolean}} options - */ - function Big5Decoder(options) { - var fatal = options.fatal; - var /** @type {number} */ big5_lead = 0x00, - /** @type {?number} */ big5_pending = null; - - /** - * @param {ByteInputStream} byte_pointer The byte steram to decode. - * @return {?number} The next code point decoded, or null if not enough - * data exists in the input stream to decode a complete code point. - */ - this.decode = function(byte_pointer) { - // NOTE: Hack to support emitting two code points - if (big5_pending !== null) { - var pending = big5_pending; - big5_pending = null; - return pending; - } - var bite = byte_pointer.get(); - if (bite === EOF_byte && big5_lead === 0x00) { - return EOF_code_point; - } - if (bite === EOF_byte && big5_lead !== 0x00) { - big5_lead = 0x00; - return decoderError(fatal); - } - byte_pointer.offset(1); - if (big5_lead !== 0x00) { - var lead = big5_lead; - var pointer = null; - big5_lead = 0x00; - var offset = bite < 0x7F ? 0x40 : 0x62; - if (inRange(bite, 0x40, 0x7E) || inRange(bite, 0xA1, 0xFE)) { - pointer = (lead - 0x81) * 157 + (bite - offset); - } - if (pointer === 1133) { - big5_pending = 0x0304; - return 0x00CA; - } - if (pointer === 1135) { - big5_pending = 0x030C; - return 0x00CA; - } - if (pointer === 1164) { - big5_pending = 0x0304; - return 0x00EA; - } - if (pointer === 1166) { - big5_pending = 0x030C; - return 0x00EA; - } - var code_point = (pointer === null) ? null : - indexCodePointFor(pointer, indexes['big5']); - if (pointer === null) { - byte_pointer.offset(-1); - } - if (code_point === null) { - return decoderError(fatal); - } - return code_point; - } - if (inRange(bite, 0x00, 0x7F)) { - return bite; - } - if (inRange(bite, 0x81, 0xFE)) { - big5_lead = bite; - return null; - } - return decoderError(fatal); - }; - } - - /** - * @constructor - * @param {{fatal: boolean}} options - */ - function Big5Encoder(options) { - var fatal = options.fatal; - /** - * @param {ByteOutputStream} output_byte_stream Output byte stream. - * @param {CodePointInputStream} code_point_pointer Input stream. - * @return {number} The last byte emitted. - */ - this.encode = function(output_byte_stream, code_point_pointer) { - var code_point = code_point_pointer.get(); - if (code_point === EOF_code_point) { - return EOF_byte; - } - code_point_pointer.offset(1); - if (inRange(code_point, 0x0000, 0x007F)) { - return output_byte_stream.emit(code_point); - } - var pointer = indexPointerFor(code_point, indexes['big5']); - if (pointer === null) { - return encoderError(code_point); - } - var lead = div(pointer, 157) + 0x81; - //if (lead < 0xA1) { - // return encoderError(code_point); - //} - var trail = pointer % 157; - var offset = trail < 0x3F ? 0x40 : 0x62; - return output_byte_stream.emit(lead, trail + offset); - }; - } - - name_to_encoding['big5'].getEncoder = function(options) { - return new Big5Encoder(options); - }; - name_to_encoding['big5'].getDecoder = function(options) { - return new Big5Decoder(options); - }; - - - // - // 11. Legacy multi-byte Japanese encodings - // - - // 11.1 euc.jp - - /** - * @constructor - * @param {{fatal: boolean}} options - */ - function EUCJPDecoder(options) { - var fatal = options.fatal; - var /** @type {number} */ eucjp_first = 0x00, - /** @type {number} */ eucjp_second = 0x00; - /** - * @param {ByteInputStream} byte_pointer The byte stream to decode. - * @return {?number} The next code point decoded, or null if not enough - * data exists in the input stream to decode a complete code point. - */ - this.decode = function(byte_pointer) { - var bite = byte_pointer.get(); - if (bite === EOF_byte) { - if (eucjp_first === 0x00 && eucjp_second === 0x00) { - return EOF_code_point; - } - eucjp_first = 0x00; - eucjp_second = 0x00; - return decoderError(fatal); - } - byte_pointer.offset(1); - - var lead, code_point; - if (eucjp_second !== 0x00) { - lead = eucjp_second; - eucjp_second = 0x00; - code_point = null; - if (inRange(lead, 0xA1, 0xFE) && inRange(bite, 0xA1, 0xFE)) { - code_point = indexCodePointFor((lead - 0xA1) * 94 + bite - 0xA1, - indexes['jis0212']); - } - if (!inRange(bite, 0xA1, 0xFE)) { - byte_pointer.offset(-1); - } - if (code_point === null) { - return decoderError(fatal); - } - return code_point; - } - if (eucjp_first === 0x8E && inRange(bite, 0xA1, 0xDF)) { - eucjp_first = 0x00; - return 0xFF61 + bite - 0xA1; - } - if (eucjp_first === 0x8F && inRange(bite, 0xA1, 0xFE)) { - eucjp_first = 0x00; - eucjp_second = bite; - return null; - } - if (eucjp_first !== 0x00) { - lead = eucjp_first; - eucjp_first = 0x00; - code_point = null; - if (inRange(lead, 0xA1, 0xFE) && inRange(bite, 0xA1, 0xFE)) { - code_point = indexCodePointFor((lead - 0xA1) * 94 + bite - 0xA1, - indexes['jis0208']); - } - if (!inRange(bite, 0xA1, 0xFE)) { - byte_pointer.offset(-1); - } - if (code_point === null) { - return decoderError(fatal); - } - return code_point; - } - if (inRange(bite, 0x00, 0x7F)) { - return bite; - } - if (bite === 0x8E || bite === 0x8F || (inRange(bite, 0xA1, 0xFE))) { - eucjp_first = bite; - return null; - } - return decoderError(fatal); - }; - } - - /** - * @constructor - * @param {{fatal: boolean}} options - */ - function EUCJPEncoder(options) { - var fatal = options.fatal; - /** - * @param {ByteOutputStream} output_byte_stream Output byte stream. - * @param {CodePointInputStream} code_point_pointer Input stream. - * @return {number} The last byte emitted. - */ - this.encode = function(output_byte_stream, code_point_pointer) { - var code_point = code_point_pointer.get(); - if (code_point === EOF_code_point) { - return EOF_byte; - } - code_point_pointer.offset(1); - if (inRange(code_point, 0x0000, 0x007F)) { - return output_byte_stream.emit(code_point); - } - if (code_point === 0x00A5) { - return output_byte_stream.emit(0x5C); - } - if (code_point === 0x203E) { - return output_byte_stream.emit(0x7E); - } - if (inRange(code_point, 0xFF61, 0xFF9F)) { - return output_byte_stream.emit(0x8E, code_point - 0xFF61 + 0xA1); - } - - var pointer = indexPointerFor(code_point, indexes['jis0208']); - if (pointer === null) { - return encoderError(code_point); - } - var lead = div(pointer, 94) + 0xA1; - var trail = pointer % 94 + 0xA1; - return output_byte_stream.emit(lead, trail); - }; - } - - name_to_encoding['euc-jp'].getEncoder = function(options) { - return new EUCJPEncoder(options); - }; - name_to_encoding['euc-jp'].getDecoder = function(options) { - return new EUCJPDecoder(options); - }; - - // 11.2 iso-2022-jp - - /** - * @constructor - * @param {{fatal: boolean}} options - */ - function ISO2022JPDecoder(options) { - var fatal = options.fatal; - /** @enum */ - var state = { - ASCII: 0, - escape_start: 1, - escape_middle: 2, - escape_final: 3, - lead: 4, - trail: 5, - Katakana: 6 - }; - var /** @type {number} */ iso2022jp_state = state.ASCII, - /** @type {boolean} */ iso2022jp_jis0212 = false, - /** @type {number} */ iso2022jp_lead = 0x00; - /** - * @param {ByteInputStream} byte_pointer The byte stream to decode. - * @return {?number} The next code point decoded, or null if not enough - * data exists in the input stream to decode a complete code point. - */ - this.decode = function(byte_pointer) { - var bite = byte_pointer.get(); - if (bite !== EOF_byte) { - byte_pointer.offset(1); - } - switch (iso2022jp_state) { - default: - case state.ASCII: - if (bite === 0x1B) { - iso2022jp_state = state.escape_start; - return null; - } - if (inRange(bite, 0x00, 0x7F)) { - return bite; - } - if (bite === EOF_byte) { - return EOF_code_point; - } - return decoderError(fatal); - - case state.escape_start: - if (bite === 0x24 || bite === 0x28) { - iso2022jp_lead = bite; - iso2022jp_state = state.escape_middle; - return null; - } - if (bite !== EOF_byte) { - byte_pointer.offset(-1); - } - iso2022jp_state = state.ASCII; - return decoderError(fatal); - - case state.escape_middle: - var lead = iso2022jp_lead; - iso2022jp_lead = 0x00; - if (lead === 0x24 && (bite === 0x40 || bite === 0x42)) { - iso2022jp_jis0212 = false; - iso2022jp_state = state.lead; - return null; - } - if (lead === 0x24 && bite === 0x28) { - iso2022jp_state = state.escape_final; - return null; - } - if (lead === 0x28 && (bite === 0x42 || bite === 0x4A)) { - iso2022jp_state = state.ASCII; - return null; - } - if (lead === 0x28 && bite === 0x49) { - iso2022jp_state = state.Katakana; - return null; - } - if (bite === EOF_byte) { - byte_pointer.offset(-1); - } else { - byte_pointer.offset(-2); - } - iso2022jp_state = state.ASCII; - return decoderError(fatal); - - case state.escape_final: - if (bite === 0x44) { - iso2022jp_jis0212 = true; - iso2022jp_state = state.lead; - return null; - } - if (bite === EOF_byte) { - byte_pointer.offset(-2); - } else { - byte_pointer.offset(-3); - } - iso2022jp_state = state.ASCII; - return decoderError(fatal); - - case state.lead: - if (bite === 0x0A) { - iso2022jp_state = state.ASCII; - return decoderError(fatal, 0x000A); - } - if (bite === 0x1B) { - iso2022jp_state = state.escape_start; - return null; - } - if (bite === EOF_byte) { - return EOF_code_point; - } - iso2022jp_lead = bite; - iso2022jp_state = state.trail; - return null; - - case state.trail: - iso2022jp_state = state.lead; - if (bite === EOF_byte) { - return decoderError(fatal); - } - var code_point = null; - var pointer = (iso2022jp_lead - 0x21) * 94 + bite - 0x21; - if (inRange(iso2022jp_lead, 0x21, 0x7E) && - inRange(bite, 0x21, 0x7E)) { - code_point = (iso2022jp_jis0212 === false) ? - indexCodePointFor(pointer, indexes['jis0208']) : - indexCodePointFor(pointer, indexes['jis0212']); - } - if (code_point === null) { - return decoderError(fatal); - } - return code_point; - - case state.Katakana: - if (bite === 0x1B) { - iso2022jp_state = state.escape_start; - return null; - } - if (inRange(bite, 0x21, 0x5F)) { - return 0xFF61 + bite - 0x21; - } - if (bite === EOF_byte) { - return EOF_code_point; - } - return decoderError(fatal); - } - }; - } - - /** - * @constructor - * @param {{fatal: boolean}} options - */ - function ISO2022JPEncoder(options) { - var fatal = options.fatal; - /** @enum */ - var state = { - ASCII: 0, - lead: 1, - Katakana: 2 - }; - var /** @type {number} */ iso2022jp_state = state.ASCII; - /** - * @param {ByteOutputStream} output_byte_stream Output byte stream. - * @param {CodePointInputStream} code_point_pointer Input stream. - * @return {number} The last byte emitted. - */ - this.encode = function(output_byte_stream, code_point_pointer) { - var code_point = code_point_pointer.get(); - if (code_point === EOF_code_point) { - return EOF_byte; - } - code_point_pointer.offset(1); - if ((inRange(code_point, 0x0000, 0x007F) || - code_point === 0x00A5 || code_point === 0x203E) && - iso2022jp_state !== state.ASCII) { - code_point_pointer.offset(-1); - iso2022jp_state = state.ASCII; - return output_byte_stream.emit(0x1B, 0x28, 0x42); - } - if (inRange(code_point, 0x0000, 0x007F)) { - return output_byte_stream.emit(code_point); - } - if (code_point === 0x00A5) { - return output_byte_stream.emit(0x5C); - } - if (code_point === 0x203E) { - return output_byte_stream.emit(0x7E); - } - if (inRange(code_point, 0xFF61, 0xFF9F) && - iso2022jp_state !== state.Katakana) { - code_point_pointer.offset(-1); - iso2022jp_state = state.Katakana; - return output_byte_stream.emit(0x1B, 0x28, 0x49); - } - if (inRange(code_point, 0xFF61, 0xFF9F)) { - return output_byte_stream.emit(code_point - 0xFF61 - 0x21); - } - if (iso2022jp_state !== state.lead) { - code_point_pointer.offset(-1); - iso2022jp_state = state.lead; - return output_byte_stream.emit(0x1B, 0x24, 0x42); - } - var pointer = indexPointerFor(code_point, indexes['jis0208']); - if (pointer === null) { - return encoderError(code_point); - } - var lead = div(pointer, 94) + 0x21; - var trail = pointer % 94 + 0x21; - return output_byte_stream.emit(lead, trail); - }; - } - - name_to_encoding['iso-2022-jp'].getEncoder = function(options) { - return new ISO2022JPEncoder(options); - }; - name_to_encoding['iso-2022-jp'].getDecoder = function(options) { - return new ISO2022JPDecoder(options); - }; - - // 11.3 shift_jis - - /** - * @constructor - * @param {{fatal: boolean}} options - */ - function ShiftJISDecoder(options) { - var fatal = options.fatal; - var /** @type {number} */ shiftjis_lead = 0x00; - /** - * @param {ByteInputStream} byte_pointer The byte stream to decode. - * @return {?number} The next code point decoded, or null if not enough - * data exists in the input stream to decode a complete code point. - */ - this.decode = function(byte_pointer) { - var bite = byte_pointer.get(); - if (bite === EOF_byte && shiftjis_lead === 0x00) { - return EOF_code_point; - } - if (bite === EOF_byte && shiftjis_lead !== 0x00) { - shiftjis_lead = 0x00; - return decoderError(fatal); - } - byte_pointer.offset(1); - if (shiftjis_lead !== 0x00) { - var lead = shiftjis_lead; - shiftjis_lead = 0x00; - if (inRange(bite, 0x40, 0x7E) || inRange(bite, 0x80, 0xFC)) { - var offset = (bite < 0x7F) ? 0x40 : 0x41; - var lead_offset = (lead < 0xA0) ? 0x81 : 0xC1; - var code_point = indexCodePointFor((lead - lead_offset) * 188 + - bite - offset, indexes['jis0208']); - if (code_point === null) { - return decoderError(fatal); - } - return code_point; - } - byte_pointer.offset(-1); - return decoderError(fatal); - } - if (inRange(bite, 0x00, 0x80)) { - return bite; - } - if (inRange(bite, 0xA1, 0xDF)) { - return 0xFF61 + bite - 0xA1; - } - if (inRange(bite, 0x81, 0x9F) || inRange(bite, 0xE0, 0xFC)) { - shiftjis_lead = bite; - return null; - } - return decoderError(fatal); - }; - } - - /** - * @constructor - * @param {{fatal: boolean}} options - */ - function ShiftJISEncoder(options) { - var fatal = options.fatal; - /** - * @param {ByteOutputStream} output_byte_stream Output byte stream. - * @param {CodePointInputStream} code_point_pointer Input stream. - * @return {number} The last byte emitted. - */ - this.encode = function(output_byte_stream, code_point_pointer) { - var code_point = code_point_pointer.get(); - if (code_point === EOF_code_point) { - return EOF_byte; - } - code_point_pointer.offset(1); - if (inRange(code_point, 0x0000, 0x0080)) { - return output_byte_stream.emit(code_point); - } - if (code_point === 0x00A5) { - return output_byte_stream.emit(0x5C); - } - if (code_point === 0x203E) { - return output_byte_stream.emit(0x7E); - } - if (inRange(code_point, 0xFF61, 0xFF9F)) { - return output_byte_stream.emit(code_point - 0xFF61 + 0xA1); - } - var pointer = indexPointerFor(code_point, indexes['jis0208']); - if (pointer === null) { - return encoderError(code_point); - } - var lead = div(pointer, 188); - var lead_offset = lead < 0x1F ? 0x81 : 0xC1; - var trail = pointer % 188; - var offset = trail < 0x3F ? 0x40 : 0x41; - return output_byte_stream.emit(lead + lead_offset, trail + offset); - }; - } - - name_to_encoding['shift_jis'].getEncoder = function(options) { - return new ShiftJISEncoder(options); - }; - name_to_encoding['shift_jis'].getDecoder = function(options) { - return new ShiftJISDecoder(options); - }; - - // - // 12. Legacy multi-byte Korean encodings - // - - // 12.1 euc-kr - - /** - * @constructor - * @param {{fatal: boolean}} options - */ - function EUCKRDecoder(options) { - var fatal = options.fatal; - var /** @type {number} */ euckr_lead = 0x00; - /** - * @param {ByteInputStream} byte_pointer The byte stream to decode. - * @return {?number} The next code point decoded, or null if not enough - * data exists in the input stream to decode a complete code point. - */ - this.decode = function(byte_pointer) { - var bite = byte_pointer.get(); - if (bite === EOF_byte && euckr_lead === 0) { - return EOF_code_point; - } - if (bite === EOF_byte && euckr_lead !== 0) { - euckr_lead = 0x00; - return decoderError(fatal); - } - byte_pointer.offset(1); - if (euckr_lead !== 0x00) { - var lead = euckr_lead; - var pointer = null; - euckr_lead = 0x00; - - if (inRange(lead, 0x81, 0xC6)) { - var temp = (26 + 26 + 126) * (lead - 0x81); - if (inRange(bite, 0x41, 0x5A)) { - pointer = temp + bite - 0x41; - } else if (inRange(bite, 0x61, 0x7A)) { - pointer = temp + 26 + bite - 0x61; - } else if (inRange(bite, 0x81, 0xFE)) { - pointer = temp + 26 + 26 + bite - 0x81; - } - } - - if (inRange(lead, 0xC7, 0xFD) && inRange(bite, 0xA1, 0xFE)) { - pointer = (26 + 26 + 126) * (0xC7 - 0x81) + (lead - 0xC7) * 94 + - (bite - 0xA1); - } - - var code_point = (pointer === null) ? null : - indexCodePointFor(pointer, indexes['euc-kr']); - if (pointer === null) { - byte_pointer.offset(-1); - } - if (code_point === null) { - return decoderError(fatal); - } - return code_point; - } - - if (inRange(bite, 0x00, 0x7F)) { - return bite; - } - - if (inRange(bite, 0x81, 0xFD)) { - euckr_lead = bite; - return null; - } - - return decoderError(fatal); - }; - } - - /** - * @constructor - * @param {{fatal: boolean}} options - */ - function EUCKREncoder(options) { - var fatal = options.fatal; - /** - * @param {ByteOutputStream} output_byte_stream Output byte stream. - * @param {CodePointInputStream} code_point_pointer Input stream. - * @return {number} The last byte emitted. - */ - this.encode = function(output_byte_stream, code_point_pointer) { - var code_point = code_point_pointer.get(); - if (code_point === EOF_code_point) { - return EOF_byte; - } - code_point_pointer.offset(1); - if (inRange(code_point, 0x0000, 0x007F)) { - return output_byte_stream.emit(code_point); - } - var pointer = indexPointerFor(code_point, indexes['euc-kr']); - if (pointer === null) { - return encoderError(code_point); - } - var lead, trail; - if (pointer < ((26 + 26 + 126) * (0xC7 - 0x81))) { - lead = div(pointer, (26 + 26 + 126)) + 0x81; - trail = pointer % (26 + 26 + 126); - var offset = trail < 26 ? 0x41 : trail < 26 + 26 ? 0x47 : 0x4D; - return output_byte_stream.emit(lead, trail + offset); - } - pointer = pointer - (26 + 26 + 126) * (0xC7 - 0x81); - lead = div(pointer, 94) + 0xC7; - trail = pointer % 94 + 0xA1; - return output_byte_stream.emit(lead, trail); - }; - } - - name_to_encoding['euc-kr'].getEncoder = function(options) { - return new EUCKREncoder(options); - }; - name_to_encoding['euc-kr'].getDecoder = function(options) { - return new EUCKRDecoder(options); - }; - - // 12.2 iso-2022-kr - - /** - * @constructor - * @param {{fatal: boolean}} options - */ - function ISO2022KRDecoder(options) { - var fatal = options.fatal; - /** @enum */ - var state = { - ASCII: 0, - escape_start: 1, - escape_middle: 2, - escape_end: 3, - lead: 4, - trail: 5 - }; - var /** @type {number} */ iso2022kr_state = state.ASCII, - /** @type {number} */ iso2022kr_lead = 0x00; - /** - * @param {ByteInputStream} byte_pointer The byte stream to decode. - * @return {?number} The next code point decoded, or null if not enough - * data exists in the input stream to decode a complete code point. - */ - this.decode = function(byte_pointer) { - var bite = byte_pointer.get(); - if (bite !== EOF_byte) { - byte_pointer.offset(1); - } - switch (iso2022kr_state) { - default: - case state.ASCII: - if (bite === 0x0E) { - iso2022kr_state = state.lead; - return null; - } - if (bite === 0x0F) { - return null; - } - if (bite === 0x1B) { - iso2022kr_state = state.escape_start; - return null; - } - if (inRange(bite, 0x00, 0x7F)) { - return bite; - } - if (bite === EOF_byte) { - return EOF_code_point; - } - return decoderError(fatal); - case state.escape_start: - if (bite === 0x24) { - iso2022kr_state = state.escape_middle; - return null; - } - if (bite !== EOF_byte) { - byte_pointer.offset(-1); - } - iso2022kr_state = state.ASCII; - return decoderError(fatal); - case state.escape_middle: - if (bite === 0x29) { - iso2022kr_state = state.escape_end; - return null; - } - if (bite === EOF_byte) { - byte_pointer.offset(-1); - } else { - byte_pointer.offset(-2); - } - iso2022kr_state = state.ASCII; - return decoderError(fatal); - case state.escape_end: - if (bite === 0x43) { - iso2022kr_state = state.ASCII; - return null; - } - if (bite === EOF_byte) { - byte_pointer.offset(-2); - } else { - byte_pointer.offset(-3); - } - iso2022kr_state = state.ASCII; - return decoderError(fatal); - case state.lead: - if (bite === 0x0A) { - iso2022kr_state = state.ASCII; - return decoderError(fatal, 0x000A); - } - if (bite === 0x0E) { - return null; - } - if (bite === 0x0F) { - iso2022kr_state = state.ASCII; - return null; - } - if (bite === EOF_byte) { - return EOF_code_point; - } - iso2022kr_lead = bite; - iso2022kr_state = state.trail; - return null; - case state.trail: - iso2022kr_state = state.lead; - if (bite === EOF_byte) { - return decoderError(fatal); - } - var code_point = null; - if (inRange(iso2022kr_lead, 0x21, 0x46) && - inRange(bite, 0x21, 0x7E)) { - code_point = indexCodePointFor((26 + 26 + 126) * - (iso2022kr_lead - 1) + - 26 + 26 + bite - 1, - indexes['euc-kr']); - } else if (inRange(iso2022kr_lead, 0x47, 0x7E) && - inRange(bite, 0x21, 0x7E)) { - code_point = indexCodePointFor((26 + 26 + 126) * (0xC7 - 0x81) + - (iso2022kr_lead - 0x47) * 94 + - (bite - 0x21), - indexes['euc-kr']); - } - if (code_point !== null) { - return code_point; - } - return decoderError(fatal); - } - }; - } - - /** - * @constructor - * @param {{fatal: boolean}} options - */ - function ISO2022KREncoder(options) { - var fatal = options.fatal; - /** @enum */ - var state = { - ASCII: 0, - lead: 1 - }; - var /** @type {boolean} */ iso2022kr_initialization = false, - /** @type {number} */ iso2022kr_state = state.ASCII; - /** - * @param {ByteOutputStream} output_byte_stream Output byte stream. - * @param {CodePointInputStream} code_point_pointer Input stream. - * @return {number} The last byte emitted. - */ - this.encode = function(output_byte_stream, code_point_pointer) { - var code_point = code_point_pointer.get(); - if (code_point === EOF_code_point) { - return EOF_byte; - } - if (!iso2022kr_initialization) { - iso2022kr_initialization = true; - output_byte_stream.emit(0x1B, 0x24, 0x29, 0x43); - } - code_point_pointer.offset(1); - if (inRange(code_point, 0x0000, 0x007F) && - iso2022kr_state !== state.ASCII) { - code_point_pointer.offset(-1); - iso2022kr_state = state.ASCII; - return output_byte_stream.emit(0x0F); - } - if (inRange(code_point, 0x0000, 0x007F)) { - return output_byte_stream.emit(code_point); - } - if (iso2022kr_state !== state.lead) { - code_point_pointer.offset(-1); - iso2022kr_state = state.lead; - return output_byte_stream.emit(0x0E); - } - var pointer = indexPointerFor(code_point, indexes['euc-kr']); - if (pointer === null) { - return encoderError(code_point); - } - var lead, trail; - if (pointer < (26 + 26 + 126) * (0xC7 - 0x81)) { - lead = div(pointer, (26 + 26 + 126)) + 1; - trail = pointer % (26 + 26 + 126) - 26 - 26 + 1; - if (!inRange(lead, 0x21, 0x46) || !inRange(trail, 0x21, 0x7E)) { - return encoderError(code_point); - } - return output_byte_stream.emit(lead, trail); - } - pointer = pointer - (26 + 26 + 126) * (0xC7 - 0x81); - lead = div(pointer, 94) + 0x47; - trail = pointer % 94 + 0x21; - if (!inRange(lead, 0x47, 0x7E) || !inRange(trail, 0x21, 0x7E)) { - return encoderError(code_point); - } - return output_byte_stream.emit(lead, trail); - }; - } - - name_to_encoding['iso-2022-kr'].getEncoder = function(options) { - return new ISO2022KREncoder(options); - }; - name_to_encoding['iso-2022-kr'].getDecoder = function(options) { - return new ISO2022KRDecoder(options); - }; - - - // - // 13. Legacy utf-16 encodings - // - - // 13.1 utf-16 - - /** - * @constructor - * @param {boolean} utf16_be True if big-endian, false if little-endian. - * @param {{fatal: boolean}} options - */ - function UTF16Decoder(utf16_be, options) { - var fatal = options.fatal; - var /** @type {?number} */ utf16_lead_byte = null, - /** @type {?number} */ utf16_lead_surrogate = null; - /** - * @param {ByteInputStream} byte_pointer The byte stream to decode. - * @return {?number} The next code point decoded, or null if not enough - * data exists in the input stream to decode a complete code point. - */ - this.decode = function(byte_pointer) { - var bite = byte_pointer.get(); - if (bite === EOF_byte && utf16_lead_byte === null && - utf16_lead_surrogate === null) { - return EOF_code_point; - } - if (bite === EOF_byte && (utf16_lead_byte !== null || - utf16_lead_surrogate !== null)) { - return decoderError(fatal); - } - byte_pointer.offset(1); - if (utf16_lead_byte === null) { - utf16_lead_byte = bite; - return null; - } - var code_point; - if (utf16_be) { - code_point = (utf16_lead_byte << 8) + bite; - } else { - code_point = (bite << 8) + utf16_lead_byte; - } - utf16_lead_byte = null; - if (utf16_lead_surrogate !== null) { - var lead_surrogate = utf16_lead_surrogate; - utf16_lead_surrogate = null; - if (inRange(code_point, 0xDC00, 0xDFFF)) { - return 0x10000 + (lead_surrogate - 0xD800) * 0x400 + - (code_point - 0xDC00); - } - byte_pointer.offset(-2); - return decoderError(fatal); - } - if (inRange(code_point, 0xD800, 0xDBFF)) { - utf16_lead_surrogate = code_point; - return null; - } - if (inRange(code_point, 0xDC00, 0xDFFF)) { - return decoderError(fatal); - } - return code_point; - }; - } - - /** - * @constructor - * @param {boolean} utf16_be True if big-endian, false if little-endian. - * @param {{fatal: boolean}} options - */ - function UTF16Encoder(utf16_be, options) { - var fatal = options.fatal; - /** - * @param {ByteOutputStream} output_byte_stream Output byte stream. - * @param {CodePointInputStream} code_point_pointer Input stream. - * @return {number} The last byte emitted. - */ - this.encode = function(output_byte_stream, code_point_pointer) { - function convert_to_bytes(code_unit) { - var byte1 = code_unit >> 8; - var byte2 = code_unit & 0x00FF; - if (utf16_be) { - return output_byte_stream.emit(byte1, byte2); - } - return output_byte_stream.emit(byte2, byte1); - } - var code_point = code_point_pointer.get(); - if (code_point === EOF_code_point) { - return EOF_byte; - } - code_point_pointer.offset(1); - if (inRange(code_point, 0xD800, 0xDFFF)) { - encoderError(code_point); - } - if (code_point <= 0xFFFF) { - return convert_to_bytes(code_point); - } - var lead = div((code_point - 0x10000), 0x400) + 0xD800; - var trail = ((code_point - 0x10000) % 0x400) + 0xDC00; - convert_to_bytes(lead); - return convert_to_bytes(trail); - }; - } - - name_to_encoding['utf-16'].getEncoder = function(options) { - return new UTF16Encoder(false, options); - }; - name_to_encoding['utf-16'].getDecoder = function(options) { - return new UTF16Decoder(false, options); - }; - - // 13.2 utf-16be - name_to_encoding['utf-16be'].getEncoder = function(options) { - return new UTF16Encoder(true, options); - }; - name_to_encoding['utf-16be'].getDecoder = function(options) { - return new UTF16Decoder(true, options); - }; - // NOTE: currently unused /** @@ -11304,7 +7067,8 @@ waitForDomReady(); // Implementation of Text Encoding Web API // - /** @const */ var DEFAULT_ENCODING = 'utf-8'; + /** @const */ + var DEFAULT_ENCODING = 'utf-8'; /** * @constructor @@ -11465,397 +7229,249 @@ waitForDomReady(); global['TextEncoder'] = global['TextEncoder'] || TextEncoder; global['TextDecoder'] = global['TextDecoder'] || TextDecoder; + + /* jshint ignore:end */ + }(this)); -/* jshint ignore:end */ -!(function() { - // Rumor Messaging for JS - // - // https://tbwiki.tokbox.com/index.php/Rumor_:_Messaging_FrameWork - // - // @todo Rumor { - // Add error codes for all the error cases - // Add Dependability commands - // } +// tb_require('../../../helpers/helpers.js') +// tb_require('./encoding.js') +// tb_require('./rumor.js') - OT.Rumor = { - MessageType: { - // This is used to subscribe to address/addresses. The address/addresses the - // client specifies here is registered on the server. Once any message is sent to - // that address/addresses, the client receives that message. - SUBSCRIBE: 0, +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT, TextEncoder, TextDecoder */ - // This is used to unsubscribe to address / addresses. Once the client unsubscribe - // to an address, it will stop getting messages sent to that address. - UNSUBSCRIBE: 1, +// +// +// @references +// * https://tbwiki.tokbox.com/index.php/Rumor_Message_Packet +// * https://tbwiki.tokbox.com/index.php/Rumor_Protocol +// +OT.Rumor.Message = function (type, toAddress, headers, data) { + this.type = type; + this.toAddress = toAddress; + this.headers = headers; + this.data = data; - // This is used to send messages to arbitrary address/ addresses. Messages can be - // anything and Rumor will not care about what is included. - MESSAGE: 2, + this.transactionId = this.headers['TRANSACTION-ID']; + this.status = this.headers.STATUS; + this.isError = !(this.status && this.status[0] === '2'); +}; - // This will be the first message that the client sends to the server. It includes - // the uniqueId for that client connection and a disconnect_notify address that will - // be notified once the client disconnects. - CONNECT: 3, +OT.Rumor.Message.prototype.serialize = function () { + var offset = 8, + cBuf = 7, + address = [], + headerKey = [], + headerVal = [], + strArray, + dataView, + i, + j; - // This will be the message used by the server to notify an address that a - // client disconnected. - DISCONNECT: 4, + // The number of addresses + cBuf++; - //Enhancements to support Keepalives - PING: 7, - PONG: 8, - STATUS: 9 + // Write out the address. + for (i = 0; i < this.toAddress.length; i++) { + /*jshint newcap:false */ + address.push(new TextEncoder('utf-8').encode(this.toAddress[i])); + cBuf += 2; + cBuf += address[i].length; + } + + // The number of parameters + cBuf++; + + // Write out the params + i = 0; + + for (var key in this.headers) { + if(!this.headers.hasOwnProperty(key)) { + continue; } + headerKey.push(new TextEncoder('utf-8').encode(key)); + headerVal.push(new TextEncoder('utf-8').encode(this.headers[key])); + cBuf += 4; + cBuf += headerKey[i].length; + cBuf += headerVal[i].length; + + i++; + } + + dataView = new TextEncoder('utf-8').encode(this.data); + cBuf += dataView.length; + + // Let's allocate a binary blob of this size + var buffer = new ArrayBuffer(cBuf); + var uint8View = new Uint8Array(buffer, 0, cBuf); + + // We don't include the header in the lenght. + cBuf -= 4; + + // Write out size (in network order) + uint8View[0] = (cBuf & 0xFF000000) >>> 24; + uint8View[1] = (cBuf & 0x00FF0000) >>> 16; + uint8View[2] = (cBuf & 0x0000FF00) >>> 8; + uint8View[3] = (cBuf & 0x000000FF) >>> 0; + + // Write out reserved bytes + uint8View[4] = 0; + uint8View[5] = 0; + + // Write out message type + uint8View[6] = this.type; + uint8View[7] = this.toAddress.length; + + // Now just copy over the encoded values.. + for (i = 0; i < address.length; i++) { + strArray = address[i]; + uint8View[offset++] = strArray.length >> 8 & 0xFF; + uint8View[offset++] = strArray.length >> 0 & 0xFF; + for (j = 0; j < strArray.length; j++) { + uint8View[offset++] = strArray[j]; + } + } + + uint8View[offset++] = headerKey.length; + + // Write out the params + for (i = 0; i < headerKey.length; i++) { + strArray = headerKey[i]; + uint8View[offset++] = strArray.length >> 8 & 0xFF; + uint8View[offset++] = strArray.length >> 0 & 0xFF; + for (j = 0; j < strArray.length; j++) { + uint8View[offset++] = strArray[j]; + } + + strArray = headerVal[i]; + uint8View[offset++] = strArray.length >> 8 & 0xFF; + uint8View[offset++] = strArray.length >> 0 & 0xFF; + for (j = 0; j < strArray.length; j++) { + uint8View[offset++] = strArray[j]; + } + } + + // And finally the data + for (i = 0; i < dataView.length; i++) { + uint8View[offset++] = dataView[i]; + } + + return buffer; +}; + +function toArrayBuffer(buffer) { + var ab = new ArrayBuffer(buffer.length); + var view = new Uint8Array(ab); + for (var i = 0; i < buffer.length; ++i) { + view[i] = buffer[i]; + } + return ab; +} + +OT.Rumor.Message.deserialize = function (buffer) { + + if(typeof Buffer !== 'undefined' && + Buffer.isBuffer(buffer)) { + buffer = toArrayBuffer(buffer); + } + var cBuf = 0, + type, + offset = 8, + uint8View = new Uint8Array(buffer), + strView, + headerlen, + headers, + keyStr, + valStr, + length, + i; + + // Write out size (in network order) + cBuf += uint8View[0] << 24; + cBuf += uint8View[1] << 16; + cBuf += uint8View[2] << 8; + cBuf += uint8View[3] << 0; + + type = uint8View[6]; + var address = []; + + for (i = 0; i < uint8View[7]; i++) { + length = uint8View[offset++] << 8; + length += uint8View[offset++]; + strView = new Uint8Array(buffer, offset, length); + /*jshint newcap:false */ + address[i] = new TextDecoder('utf-8').decode(strView); + offset += length; + } + + headerlen = uint8View[offset++]; + headers = {}; + + for (i = 0; i < headerlen; i++) { + length = uint8View[offset++] << 8; + length += uint8View[offset++]; + strView = new Uint8Array(buffer, offset, length); + keyStr = new TextDecoder('utf-8').decode(strView); + offset += length; + + length = uint8View[offset++] << 8; + length += uint8View[offset++]; + strView = new Uint8Array(buffer, offset, length); + valStr = new TextDecoder('utf-8').decode(strView); + headers[keyStr] = valStr; + offset += length; + } + + var dataView = new Uint8Array(buffer, offset); + var data = new TextDecoder('utf-8').decode(dataView); + + return new OT.Rumor.Message(type, address, headers, data); +}; + + +OT.Rumor.Message.Connect = function (uniqueId, notifyDisconnectAddress) { + var headers = { + uniqueId: uniqueId, + notifyDisconnectAddress: notifyDisconnectAddress }; -}(this)); -!(function(OT) { + return new OT.Rumor.Message(OT.Rumor.MessageType.CONNECT, [], headers, ''); +}; - var WEB_SOCKET_KEEP_ALIVE_INTERVAL = 9000, +OT.Rumor.Message.Disconnect = function () { + return new OT.Rumor.Message(OT.Rumor.MessageType.DISCONNECT, [], {}, ''); +}; - // Magic Connectivity Timeout Constant: We wait 9*the keep alive interval, - // on the third keep alive we trigger the timeout if we haven't received the - // server pong. - WEB_SOCKET_CONNECTIVITY_TIMEOUT = 5*WEB_SOCKET_KEEP_ALIVE_INTERVAL - 100, +OT.Rumor.Message.Subscribe = function(topics) { + return new OT.Rumor.Message(OT.Rumor.MessageType.SUBSCRIBE, topics, {}, ''); +}; - wsCloseErrorCodes; +OT.Rumor.Message.Unsubscribe = function(topics) { + return new OT.Rumor.Message(OT.Rumor.MessageType.UNSUBSCRIBE, topics, {}, ''); +}; +OT.Rumor.Message.Publish = function(topics, message, headers) { + return new OT.Rumor.Message(OT.Rumor.MessageType.MESSAGE, topics, headers||{}, message || ''); +}; +// This message is used to implement keepalives on the persistent +// socket connection between the client and server. Every time the +// client sends a PING to the server, the server will respond with +// a PONG. +OT.Rumor.Message.Ping = function() { + return new OT.Rumor.Message(OT.Rumor.MessageType.PING, [], {}, ''); +}; - // https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Close_codes - // http://docs.oracle.com/javaee/7/api/javax/websocket/CloseReason.CloseCodes.html - wsCloseErrorCodes = { - 1002: 'The endpoint is terminating the connection due to a protocol error. ' + - '(CLOSE_PROTOCOL_ERROR)', - 1003: 'The connection is being terminated because the endpoint received data of ' + - 'a type it cannot accept (for example, a text-only endpoint received binary data). ' + - '(CLOSE_UNSUPPORTED)', - 1004: 'The endpoint is terminating the connection because a data frame was received ' + - 'that is too large. (CLOSE_TOO_LARGE)', - 1005: 'Indicates that no status code was provided even though one was expected. ' + - '(CLOSE_NO_STATUS)', - 1006: 'Used to indicate that a connection was closed abnormally (that is, with no ' + - 'close frame being sent) when a status code is expected. (CLOSE_ABNORMAL)', - 1007: 'Indicates that an endpoint is terminating the connection because it has received ' + - 'data within a message that was not consistent with the type of the message (e.g., ' + - 'non-UTF-8 [RFC3629] data within a text message)', - 1008: 'Indicates that an endpoint is terminating the connection because it has received a ' + - 'message that violates its policy. This is a generic status code that can be returned ' + - 'when there is no other more suitable status code (e.g., 1003 or 1009) or if there is a ' + - 'need to hide specific details about the policy', - 1009: 'Indicates that an endpoint is terminating the connection because it has received a ' + - 'message that is too big for it to process', - 1011: 'Indicates that a server is terminating the connection because it encountered an ' + - 'unexpected condition that prevented it from fulfilling the request', +// tb_require('../../../helpers/helpers.js') +// tb_require('./rumor.js') +// tb_require('./message.js') - // .... codes in the 4000-4999 range are available for use by applications. - 4001: 'Connectivity loss was detected as it was too long since the socket received the ' + - 'last PONG message' - }; +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ - OT.Rumor.SocketError = function(code, message) { - this.code = code; - this.message = message; - }; - - // The NativeSocket bit is purely to make testing simpler, it defaults to WebSocket - // so in normal operation you would omit it. - OT.Rumor.Socket = function(messagingURL, notifyDisconnectAddress, NativeSocket) { - - var states = ['disconnected', 'error', 'connected', 'connecting', 'disconnecting'], - webSocket, - id, - onOpen, - onError, - onClose, - onMessage, - connectCallback, - connectTimeout, - lastMessageTimestamp, // The timestamp of the last message received - keepAliveTimer; // Timer for the connectivity checks - - - //// Private API - var stateChanged = function(newState) { - switch (newState) { - case 'disconnected': - case 'error': - webSocket = null; - if (onClose) { - var error; - if(hasLostConnectivity()) { - error = new Error(wsCloseErrorCodes[4001]); - error.code = 4001; - } - onClose(error); - } - break; - } - }, - - setState = OT.$.statable(this, states, 'disconnected', stateChanged), - - validateCallback = function validateCallback (name, callback) { - if (callback === null || !OT.$.isFunction(callback) ) { - throw new Error('The Rumor.Socket ' + name + - ' callback must be a valid function or null'); - } - }, - - error = OT.$.bind(function error (errorMessage) { - OT.error('Rumor.Socket: ' + errorMessage); - - var socketError = new OT.Rumor.SocketError(null, errorMessage || 'Unknown Socket Error'); - - if (connectTimeout) clearTimeout(connectTimeout); - - setState('error'); - - if (this.previousState === 'connecting' && connectCallback) { - connectCallback(socketError, null); - connectCallback = null; - } - - if (onError) onError(socketError); - }, this), - - hasLostConnectivity = function hasLostConnectivity () { - if (!lastMessageTimestamp) return false; - - return (OT.$.now() - lastMessageTimestamp) >= WEB_SOCKET_CONNECTIVITY_TIMEOUT; - }, - - sendKeepAlive = OT.$.bind(function() { - if (!this.is('connected')) return; - - if ( hasLostConnectivity() ) { - webSocketDisconnected({code: 4001}); - } - else { - webSocket.send(OT.Rumor.Message.Ping()); - keepAliveTimer = setTimeout(sendKeepAlive, WEB_SOCKET_KEEP_ALIVE_INTERVAL); - } - }, this), - - // Returns true if we think the DOM has been unloaded - // It detects this by looking for the OT global, which - // should always exist until the DOM is cleaned up. - isDOMUnloaded = function isDOMUnloaded () { - return !window.OT; - }; - - - //// Private Event Handlers - var webSocketConnected = OT.$.bind(function webSocketConnected () { - if (connectTimeout) clearTimeout(connectTimeout); - if (this.isNot('connecting')) { - OT.debug('webSocketConnected reached in state other than connecting'); - return; - } - - // Connect to Rumor by registering our connection id and the - // app server address to notify if we disconnect. - // - // We don't need to wait for a reply to this message. - webSocket.send(OT.Rumor.Message.Connect(id, notifyDisconnectAddress)); - - setState('connected'); - if (connectCallback) { - connectCallback(null, id); - connectCallback = null; - } - - if (onOpen) onOpen(id); - - keepAliveTimer = setTimeout(function() { - lastMessageTimestamp = OT.$.now(); - sendKeepAlive(); - }, WEB_SOCKET_KEEP_ALIVE_INTERVAL); - }, this), - - webSocketConnectTimedOut = function webSocketConnectTimedOut () { - var webSocketWas = webSocket; - error('Timed out while waiting for the Rumor socket to connect.'); - // This will prevent a socket eventually connecting - // But call it _after_ the error just in case any of - // the callbacks fire synchronously, breaking the error - // handling code. - try { - webSocketWas.close(); - } catch(x) {} - }, - - webSocketError = function webSocketError () {}, - // var errorMessage = 'Unknown Socket Error'; - // @fixme We MUST be able to do better than this! - - // All errors seem to result in disconnecting the socket, the close event - // has a close reason and code which gives some error context. This, - // combined with the fact that the errorEvent argument contains no - // error info at all, means we'll delay triggering the error handlers - // until the socket is closed. - // error(errorMessage); - - webSocketDisconnected = OT.$.bind(function webSocketDisconnected (closeEvent) { - if (connectTimeout) clearTimeout(connectTimeout); - if (keepAliveTimer) clearTimeout(keepAliveTimer); - - if (isDOMUnloaded()) { - // Sometimes we receive the web socket close event after - // the DOM has already been partially or fully unloaded - // if that's the case here then it's not really safe, or - // desirable, to continue. - return; - } - - if (closeEvent.code !== 1000 && closeEvent.code !== 1001) { - var reason = closeEvent.reason || closeEvent.message; - if (!reason && wsCloseErrorCodes.hasOwnProperty(closeEvent.code)) { - reason = wsCloseErrorCodes[closeEvent.code]; - } - - error('Rumor Socket Disconnected: ' + reason); - } - - if (this.isNot('error')) setState('disconnected'); - }, this), - - webSocketReceivedMessage = function webSocketReceivedMessage (msg) { - lastMessageTimestamp = OT.$.now(); - - if (onMessage) { - if (msg.type !== OT.Rumor.MessageType.PONG) { - onMessage(msg); - } - } - }; - - - //// Public API - - this.publish = function (topics, message, headers) { - webSocket.send(OT.Rumor.Message.Publish(topics, message, headers)); - }; - - this.subscribe = function(topics) { - webSocket.send(OT.Rumor.Message.Subscribe(topics)); - }; - - this.unsubscribe = function(topics) { - webSocket.send(OT.Rumor.Message.Unsubscribe(topics)); - }; - - this.connect = function (connectionId, complete) { - if (this.is('connecting', 'connected')) { - complete(new OT.Rumor.SocketError(null, - 'Rumor.Socket cannot connect when it is already connecting or connected.')); - return; - } - - id = connectionId; - connectCallback = complete; - - setState('connecting'); - - var TheWebSocket = NativeSocket || window.WebSocket; - - var events = { - onOpen: webSocketConnected, - onClose: webSocketDisconnected, - onError: webSocketError, - onMessage: webSocketReceivedMessage - }; - - try { - if(typeof TheWebSocket !== 'undefined') { - webSocket = new OT.Rumor.NativeSocket(TheWebSocket, messagingURL, events); - } else { - webSocket = new OT.Rumor.PluginSocket(messagingURL, events); - } - - connectTimeout = setTimeout(webSocketConnectTimedOut, OT.Rumor.Socket.CONNECT_TIMEOUT); - } - catch(e) { - OT.error(e); - - // @todo add an actual error message - error('Could not connect to the Rumor socket, possibly because of a blocked port.'); - } - }; - - this.disconnect = function(drainSocketBuffer) { - if (connectTimeout) clearTimeout(connectTimeout); - if (keepAliveTimer) clearTimeout(keepAliveTimer); - - if (!webSocket) { - if (this.isNot('error')) setState('disconnected'); - return; - } - - if (webSocket.isClosed()) { - if (this.isNot('error')) setState('disconnected'); - } - else { - if (this.is('connected')) { - // Look! We are nice to the rumor server ;-) - webSocket.send(OT.Rumor.Message.Disconnect()); - } - - // Wait until the socket is ready to close - webSocket.close(drainSocketBuffer); - } - }; - - - - OT.$.defineProperties(this, { - id: { - get: function() { return id; } - }, - - onOpen: { - set: function(callback) { - validateCallback('onOpen', callback); - onOpen = callback; - }, - - get: function() { return onOpen; } - }, - - onError: { - set: function(callback) { - validateCallback('onError', callback); - onError = callback; - }, - - get: function() { return onError; } - }, - - onClose: { - set: function(callback) { - validateCallback('onClose', callback); - onClose = callback; - }, - - get: function() { return onClose; } - }, - - onMessage: { - set: function(callback) { - validateCallback('onMessage', callback); - onMessage = callback; - }, - - get: function() { return onMessage; } - } - }); - }; - - // The number of ms to wait for the websocket to connect - OT.Rumor.Socket.CONNECT_TIMEOUT = 15000; - -}(window.OT, this)); !(function() { var BUFFER_DRAIN_INTERVAL = 100, @@ -11931,1126 +7547,771 @@ waitForDomReady(); }(this)); -!(function() { - OT.Rumor.PluginSocket = function(messagingURL, events) { - - var webSocket, - state = 'initializing'; - - TBPlugin.initRumorSocket(messagingURL, OT.$.bind(function(err, rumorSocket) { - if(err) { - state = 'closed'; - events.onClose({ code: 4999 }); - } else if(state === 'initializing') { - webSocket = rumorSocket; - - webSocket.onOpen(function() { - state = 'open'; - events.onOpen(); - }); - webSocket.onClose(function(error) { - state = 'closed'; /* CLOSED */ - events.onClose({ code: error }); - }); - webSocket.onError(function(error) { - state = 'closed'; /* CLOSED */ - events.onError(error); - /* native websockets seem to do this, so should we */ - events.onClose({ code: error }); - }); - - webSocket.onMessage(function(type, addresses, headers, payload) { - var msg = new OT.Rumor.Message(type, addresses, headers, payload); - events.onMessage(msg); - }); - - webSocket.open(); - } else { - this.close(); - } - }, this)); - - this.close = function() { - if(state === 'initializing' || state === 'closed') { - state = 'closed'; - return; - } - - webSocket.close(1000, ''); - }; - - this.send = function(msg) { - if(state === 'open') { - webSocket.send(msg); - } - }; - - this.isClosed = function() { - return state === 'closed'; - }; - - }; - -}(this)); -!(function() { - - /*global TextEncoder, TextDecoder */ - - // - // - // @references - // * https://tbwiki.tokbox.com/index.php/Rumor_Message_Packet - // * https://tbwiki.tokbox.com/index.php/Rumor_Protocol - // - OT.Rumor.Message = function (type, toAddress, headers, data) { - this.type = type; - this.toAddress = toAddress; - this.headers = headers; - this.data = data; - - this.transactionId = this.headers['TRANSACTION-ID']; - this.status = this.headers.STATUS; - this.isError = !(this.status && this.status[0] === '2'); - }; - - OT.Rumor.Message.prototype.serialize = function () { - var offset = 8, - cBuf = 7, - address = [], - headerKey = [], - headerVal = [], - strArray, - dataView, - i, - j; - - // The number of addresses - cBuf++; - - // Write out the address. - for (i = 0; i < this.toAddress.length; i++) { - /*jshint newcap:false */ - address.push(new TextEncoder('utf-8').encode(this.toAddress[i])); - cBuf += 2; - cBuf += address[i].length; - } - - // The number of parameters - cBuf++; - - // Write out the params - i = 0; - - for (var key in this.headers) { - if(!this.headers.hasOwnProperty(key)) { - continue; - } - headerKey.push(new TextEncoder('utf-8').encode(key)); - headerVal.push(new TextEncoder('utf-8').encode(this.headers[key])); - cBuf += 4; - cBuf += headerKey[i].length; - cBuf += headerVal[i].length; - - i++; - } - - dataView = new TextEncoder('utf-8').encode(this.data); - cBuf += dataView.length; - - // Let's allocate a binary blob of this size - var buffer = new ArrayBuffer(cBuf); - var uint8View = new Uint8Array(buffer, 0, cBuf); - - // We don't include the header in the lenght. - cBuf -= 4; - - // Write out size (in network order) - uint8View[0] = (cBuf & 0xFF000000) >>> 24; - uint8View[1] = (cBuf & 0x00FF0000) >>> 16; - uint8View[2] = (cBuf & 0x0000FF00) >>> 8; - uint8View[3] = (cBuf & 0x000000FF) >>> 0; - - // Write out reserved bytes - uint8View[4] = 0; - uint8View[5] = 0; - - // Write out message type - uint8View[6] = this.type; - uint8View[7] = this.toAddress.length; - - // Now just copy over the encoded values.. - for (i = 0; i < address.length; i++) { - strArray = address[i]; - uint8View[offset++] = strArray.length >> 8 & 0xFF; - uint8View[offset++] = strArray.length >> 0 & 0xFF; - for (j = 0; j < strArray.length; j++) { - uint8View[offset++] = strArray[j]; - } - } - - uint8View[offset++] = headerKey.length; - - // Write out the params - for (i = 0; i < headerKey.length; i++) { - strArray = headerKey[i]; - uint8View[offset++] = strArray.length >> 8 & 0xFF; - uint8View[offset++] = strArray.length >> 0 & 0xFF; - for (j = 0; j < strArray.length; j++) { - uint8View[offset++] = strArray[j]; - } - - strArray = headerVal[i]; - uint8View[offset++] = strArray.length >> 8 & 0xFF; - uint8View[offset++] = strArray.length >> 0 & 0xFF; - for (j = 0; j < strArray.length; j++) { - uint8View[offset++] = strArray[j]; - } - } - - // And finally the data - for (i = 0; i < dataView.length; i++) { - uint8View[offset++] = dataView[i]; - } - - return buffer; - }; - - function toArrayBuffer(buffer) { - var ab = new ArrayBuffer(buffer.length); - var view = new Uint8Array(ab); - for (var i = 0; i < buffer.length; ++i) { - view[i] = buffer[i]; - } - return ab; - } - - OT.Rumor.Message.deserialize = function (buffer) { - - if(typeof Buffer !== 'undefined' && - Buffer.isBuffer(buffer)) { - buffer = toArrayBuffer(buffer); - } - var cBuf = 0, - type, - offset = 8, - uint8View = new Uint8Array(buffer), - strView, - headerlen, - headers, - keyStr, - valStr, - length, - i; - - // Write out size (in network order) - cBuf += uint8View[0] << 24; - cBuf += uint8View[1] << 16; - cBuf += uint8View[2] << 8; - cBuf += uint8View[3] << 0; - - type = uint8View[6]; - var address = []; - - for (i = 0; i < uint8View[7]; i++) { - length = uint8View[offset++] << 8; - length += uint8View[offset++]; - strView = new Uint8Array(buffer, offset, length); - /*jshint newcap:false */ - address[i] = new TextDecoder('utf-8').decode(strView); - offset += length; - } - - headerlen = uint8View[offset++]; - headers = {}; - - for (i = 0; i < headerlen; i++) { - length = uint8View[offset++] << 8; - length += uint8View[offset++]; - strView = new Uint8Array(buffer, offset, length); - keyStr = new TextDecoder('utf-8').decode(strView); - offset += length; - - length = uint8View[offset++] << 8; - length += uint8View[offset++]; - strView = new Uint8Array(buffer, offset, length); - valStr = new TextDecoder('utf-8').decode(strView); - headers[keyStr] = valStr; - offset += length; - } - - var dataView = new Uint8Array(buffer, offset); - var data = new TextDecoder('utf-8').decode(dataView); - - return new OT.Rumor.Message(type, address, headers, data); - }; - - - OT.Rumor.Message.Connect = function (uniqueId, notifyDisconnectAddress) { - var headers = { - uniqueId: uniqueId, - notifyDisconnectAddress: notifyDisconnectAddress - }; - - return new OT.Rumor.Message(OT.Rumor.MessageType.CONNECT, [], headers, ''); - }; - - OT.Rumor.Message.Disconnect = function () { - return new OT.Rumor.Message(OT.Rumor.MessageType.DISCONNECT, [], {}, ''); - }; - - OT.Rumor.Message.Subscribe = function(topics) { - return new OT.Rumor.Message(OT.Rumor.MessageType.SUBSCRIBE, topics, {}, ''); - }; - - OT.Rumor.Message.Unsubscribe = function(topics) { - return new OT.Rumor.Message(OT.Rumor.MessageType.UNSUBSCRIBE, topics, {}, ''); - }; - - OT.Rumor.Message.Publish = function(topics, message, headers) { - return new OT.Rumor.Message(OT.Rumor.MessageType.MESSAGE, topics, headers||{}, message || ''); - }; - - // This message is used to implement keepalives on the persistent - // socket connection between the client and server. Every time the - // client sends a PING to the server, the server will respond with - // a PONG. - OT.Rumor.Message.Ping = function() { - return new OT.Rumor.Message(OT.Rumor.MessageType.PING, [], {}, ''); - }; - -}(this)); -!(function() { - - // Rumor Messaging for JS - // - // https://tbwiki.tokbox.com/index.php/Raptor_Messages_(Sent_as_a_RumorMessage_payload_in_JSON) - // - // @todo Raptor { - // Look at disconnection cleanup: i.e. subscriber + publisher cleanup - // Add error codes for all the error cases - // Write unit tests for SessionInfo - // Write unit tests for Session - // Make use of the new DestroyedEvent - // Remove dependency on OT.properties - // OT.Capabilities must be part of the Raptor namespace - // Add Dependability commands - // Think about noConflict, or whether we should just use the OT namespace - // Think about how to expose OT.publishers, OT.subscribers, and OT.sessions if messaging was - // being included as a component - // Another solution to the problem of having publishers/subscribers/etc would be to make - // Raptor Socket a separate component from Dispatch (dispatch being more business logic) - // Look at the coupling of OT.sessions to OT.Raptor.Socket - // } - // - // @todo Raptor Docs { - // Document payload formats for incoming messages (what are the payloads for - // STREAM CREATED/MODIFIED for example) - // Document how keepalives work - // Document all the Raptor actions and types - // Document the session connect flow (including error cases) - // } - - OT.Raptor = { - Actions: { - //General - CONNECT: 100, - CREATE: 101, - UPDATE: 102, - DELETE: 103, - STATE: 104, - - //Moderation - FORCE_DISCONNECT: 105, - FORCE_UNPUBLISH: 106, - SIGNAL: 107, - - //Archives - CREATE_ARCHIVE: 108, - CLOSE_ARCHIVE: 109, - START_RECORDING_SESSION: 110, - STOP_RECORDING_SESSION: 111, - START_RECORDING_STREAM: 112, - STOP_RECORDING_STREAM: 113, - LOAD_ARCHIVE: 114, - START_PLAYBACK: 115, - STOP_PLAYBACK: 116, - - //AppState - APPSTATE_PUT: 117, - APPSTATE_DELETE: 118, - - // JSEP - OFFER: 119, - ANSWER: 120, - PRANSWER: 121, - CANDIDATE: 122, - SUBSCRIBE: 123, - UNSUBSCRIBE: 124, - QUERY: 125, - SDP_ANSWER: 126, - - //KeepAlive - PONG: 127, - REGISTER: 128, //Used for registering streams. - - QUALITY_CHANGED: 129 - }, - - Types: { - //RPC - RPC_REQUEST: 100, - RPC_RESPONSE: 101, - - //EVENT - STREAM: 102, - ARCHIVE: 103, - CONNECTION: 104, - APPSTATE: 105, - CONNECTIONCOUNT: 106, - MODERATION: 107, - SIGNAL: 108, - SUBSCRIBER: 110, - - //JSEP Protocol - JSEP: 109 - } - }; - -}(this)); -!(function() { - - - OT.Raptor.serializeMessage = function (message) { - return JSON.stringify(message); - }; - - - // Deserialising a Raptor message mainly means doing a JSON.parse on it. - // We do decorate the final message with a few extra helper properies though. - // - // These include: - // * typeName: A human readable version of the Raptor type. E.g. STREAM instead of 102 - // * actionName: A human readable version of the Raptor action. E.g. CREATE instead of 101 - // * signature: typeName and actionName combined. This is mainly for debugging. E.g. A type - // of 102 and an action of 101 would result in a signature of "STREAM:CREATE" - // - OT.Raptor.deserializeMessage = function (msg) { - if (msg.length === 0) return {}; - - var message = JSON.parse(msg), - bits = message.uri.substr(1).split('/'); - - // Remove the Raptor protocol version - bits.shift(); - if (bits[bits.length-1] === '') bits.pop(); - - message.params = {}; - for (var i=0, numBits=bits.length ; i 6) { - message.resource = bits[bits.length-4] + '_' + bits[bits.length-2]; - } else { - message.resource = bits[bits.length-2]; - } - } - else { - if (bits[bits.length-1] === 'channel' && bits.length > 5) { - message.resource = bits[bits.length-3] + '_' + bits[bits.length-1]; - } else { - message.resource = bits[bits.length-1]; - } - } - - message.signature = message.resource + '#' + message.method; - return message; - }; - - OT.Raptor.unboxFromRumorMessage = function (rumorMessage) { - var message = OT.Raptor.deserializeMessage(rumorMessage.data); - message.transactionId = rumorMessage.transactionId; - message.fromAddress = rumorMessage.headers['X-TB-FROM-ADDRESS']; - - return message; - }; - - OT.Raptor.parseIceServers = function (message) { - try { - return JSON.parse(message.data).content.iceServers; - } catch (e) { - return []; - } - }; - - OT.Raptor.Message = {}; - - - OT.Raptor.Message.connections = {}; - - OT.Raptor.Message.connections.create = function (apiKey, sessionId, connectionId) { - return OT.Raptor.serializeMessage({ - method: 'create', - uri: '/v2/partner/' + apiKey + '/session/' + sessionId + '/connection/' + connectionId, - content: { - userAgent: OT.$.userAgent() - } - }); - }; - - OT.Raptor.Message.connections.destroy = function (apiKey, sessionId, connectionId) { - return OT.Raptor.serializeMessage({ - method: 'delete', - uri: '/v2/partner/' + apiKey + '/session/' + sessionId + '/connection/' + connectionId, - content: {} - }); - }; - - - OT.Raptor.Message.sessions = {}; - - OT.Raptor.Message.sessions.get = function (apiKey, sessionId) { - return OT.Raptor.serializeMessage({ - method: 'read', - uri: '/v2/partner/' + apiKey + '/session/' + sessionId, - content: {} - }); - }; - - - OT.Raptor.Message.streams = {}; - - OT.Raptor.Message.streams.get = function (apiKey, sessionId, streamId) { - return OT.Raptor.serializeMessage({ - method: 'read', - uri: '/v2/partner/' + apiKey + '/session/' + sessionId + '/stream/' + streamId, - content: {} - }); - }; - - OT.Raptor.Message.streams.create = function (apiKey, sessionId, streamId, name, videoOrientation, - videoWidth, videoHeight, hasAudio, hasVideo, frameRate, minBitrate, maxBitrate) { - var channels = []; - - if (hasAudio !== void 0) { - channels.push({ - id: 'audio1', - type: 'audio', - active: hasAudio - }); - } - - if (hasVideo !== void 0) { - var channel = { - id: 'video1', - type: 'video', - active: hasVideo, - width: videoWidth, - height: videoHeight, - orientation: videoOrientation - }; - if (frameRate) channel.frameRate = frameRate; - channels.push(channel); - } - - var messageContent = { - id: streamId, - name: name, - channel: channels - }; - - if (minBitrate) messageContent.minBitrate = minBitrate; - if (maxBitrate) messageContent.maxBitrate = maxBitrate; - - return OT.Raptor.serializeMessage({ - method: 'create', - uri: '/v2/partner/' + apiKey + '/session/' + sessionId + '/stream/' + streamId, - content: messageContent - }); - }; - - OT.Raptor.Message.streams.destroy = function (apiKey, sessionId, streamId) { - return OT.Raptor.serializeMessage({ - method: 'delete', - uri: '/v2/partner/' + apiKey + '/session/' + sessionId + '/stream/' + streamId, - content: {} - }); - }; - - OT.Raptor.Message.streams.offer = function (apiKey, sessionId, streamId, offerSdp) { - return OT.Raptor.serializeMessage({ - method: 'offer', - uri: '/v2/partner/' + apiKey + '/session/' + sessionId + '/stream/' + streamId, - content: { - sdp: offerSdp - } - }); - }; - - OT.Raptor.Message.streams.answer = function (apiKey, sessionId, streamId, answerSdp) { - return OT.Raptor.serializeMessage({ - method: 'answer', - uri: '/v2/partner/' + apiKey + '/session/' + sessionId + '/stream/' + streamId, - content: { - sdp: answerSdp - } - }); - }; - - OT.Raptor.Message.streams.candidate = function (apiKey, sessionId, streamId, candidate) { - return OT.Raptor.serializeMessage({ - method: 'candidate', - uri: '/v2/partner/' + apiKey + '/session/' + sessionId + '/stream/' + streamId, - content: candidate - }); - }; - - OT.Raptor.Message.streamChannels = {}; - OT.Raptor.Message.streamChannels.update = - function (apiKey, sessionId, streamId, channelId, attributes) { - return OT.Raptor.serializeMessage({ - method: 'update', - uri: '/v2/partner/' + apiKey + '/session/' + sessionId + '/stream/' + - streamId + '/channel/' + channelId, - content: attributes - }); - }; - - - OT.Raptor.Message.subscribers = {}; - - OT.Raptor.Message.subscribers.create = - function (apiKey, sessionId, streamId, subscriberId, connectionId, channelsToSubscribeTo) { - var content = { - id: subscriberId, - connection: connectionId, - keyManagementMethod: OT.$.supportedCryptoScheme(), - bundleSupport: OT.$.hasCapabilities('bundle'), - rtcpMuxSupport: OT.$.hasCapabilities('RTCPMux') - }; - if (channelsToSubscribeTo) content.channel = channelsToSubscribeTo; - - return OT.Raptor.serializeMessage({ - method: 'create', - uri: '/v2/partner/' + apiKey + '/session/' + sessionId + - '/stream/' + streamId + '/subscriber/' + subscriberId, - content: content - }); - }; - - OT.Raptor.Message.subscribers.destroy = function (apiKey, sessionId, streamId, subscriberId) { - return OT.Raptor.serializeMessage({ - method: 'delete', - uri: '/v2/partner/' + apiKey + '/session/' + sessionId + - '/stream/' + streamId + '/subscriber/' + subscriberId, - content: {} - }); - }; - - OT.Raptor.Message.subscribers.update = - function (apiKey, sessionId, streamId, subscriberId, attributes) { - return OT.Raptor.serializeMessage({ - method: 'update', - uri: '/v2/partner/' + apiKey + '/session/' + sessionId + - '/stream/' + streamId + '/subscriber/' + subscriberId, - content: attributes - }); - }; - - - OT.Raptor.Message.subscribers.candidate = - function (apiKey, sessionId, streamId, subscriberId, candidate) { - return OT.Raptor.serializeMessage({ - method: 'candidate', - uri: '/v2/partner/' + apiKey + '/session/' + sessionId + - '/stream/' + streamId + '/subscriber/' + subscriberId, - content: candidate - }); - }; - - OT.Raptor.Message.subscribers.offer = - function (apiKey, sessionId, streamId, subscriberId, offerSdp) { - return OT.Raptor.serializeMessage({ - method: 'offer', - uri: '/v2/partner/' + apiKey + '/session/' + sessionId + - '/stream/' + streamId + '/subscriber/' + subscriberId, - content: { - sdp: offerSdp - } - }); - }; - - OT.Raptor.Message.subscribers.answer = - function (apiKey, sessionId, streamId, subscriberId, answerSdp) { - return OT.Raptor.serializeMessage({ - method: 'answer', - uri: '/v2/partner/' + apiKey + '/session/' + sessionId + - '/stream/' + streamId + '/subscriber/' + subscriberId, - content: { - sdp: answerSdp - } - }); - }; - - - OT.Raptor.Message.subscriberChannels = {}; - - OT.Raptor.Message.subscriberChannels.update = - function (apiKey, sessionId, streamId, subscriberId, channelId, attributes) { - return OT.Raptor.serializeMessage({ - method: 'update', - uri: '/v2/partner/' + apiKey + '/session/' + sessionId + - '/stream/' + streamId + '/subscriber/' + subscriberId + '/channel/' + channelId, - content: attributes - }); - }; - - - OT.Raptor.Message.signals = {}; - - OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type, data) { - var content = {}; - if (type !== void 0) content.type = type; - if (data !== void 0) content.data = data; - - return OT.Raptor.serializeMessage({ - method: 'signal', - uri: '/v2/partner/' + apiKey + '/session/' + sessionId + - (toAddress !== void 0 ? '/connection/' + toAddress : '') + '/signal/' + OT.$.uuid(), - content: content - }); - }; - -}(this)); -!(function() { - - var MAX_SIGNAL_DATA_LENGTH = 8192, - MAX_SIGNAL_TYPE_LENGTH = 128; - - // - // Error Codes: - // 413 - Type too long - // 400 - Type is invalid - // 413 - Data too long - // 400 - Data is invalid (can't be parsed as JSON) - // 429 - Rate limit exceeded - // 500 - Websocket connection is down - // 404 - To connection does not exist - // 400 - To is invalid - // - OT.Signal = function(sessionId, fromConnectionId, options) { - var isInvalidType = function(type) { - // Our format matches the unreserved characters from the URI RFC: - // http://www.ietf.org/rfc/rfc3986 - return !/^[a-zA-Z0-9\-\._~]+$/.exec(type); - }, - - validateTo = function(toAddress) { - if (!toAddress) { - return { - code: 400, - reason: 'The signal type was null or an empty String. Either set it to a non-empty ' + - 'String value or omit it' - }; - } - - if ( !(toAddress instanceof OT.Connection || toAddress instanceof OT.Session) ) { - return { - code: 400, - reason: 'The To field was invalid' - }; - } - - return null; - }, - - validateType = function(type) { - var error = null; - - if (type === null || type === void 0) { - error = { - code: 400, - reason: 'The signal type was null or undefined. Either set it to a String value or ' + - 'omit it' - }; - } - else if (type.length > MAX_SIGNAL_TYPE_LENGTH) { - error = { - code: 413, - reason: 'The signal type was too long, the maximum length of it is ' + - MAX_SIGNAL_TYPE_LENGTH + ' characters' - }; - } - else if ( isInvalidType(type) ) { - error = { - code: 400, - reason: 'The signal type was invalid, it can only contain letters, ' + - 'numbers, \'-\', \'_\', and \'~\'.' - }; - } - - return error; - }, - - validateData = function(data) { - var error = null; - if (data === null || data === void 0) { - error = { - code: 400, - reason: 'The signal data was null or undefined. Either set it to a String value or ' + - 'omit it' - }; - } - else { - try { - if (JSON.stringify(data).length > MAX_SIGNAL_DATA_LENGTH) { - error = { - code: 413, - reason: 'The data field was too long, the maximum size of it is ' + - MAX_SIGNAL_DATA_LENGTH + ' characters' - }; +// tb_require('../../../helpers/helpers.js') +// tb_require('./message.js') +// tb_require('./native_socket.js') +// tb_require('./plugin_socket.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ + +var WEB_SOCKET_KEEP_ALIVE_INTERVAL = 9000, + + // Magic Connectivity Timeout Constant: We wait 9*the keep alive interval, + // on the third keep alive we trigger the timeout if we haven't received the + // server pong. + WEB_SOCKET_CONNECTIVITY_TIMEOUT = 5*WEB_SOCKET_KEEP_ALIVE_INTERVAL - 100, + + wsCloseErrorCodes; + +// https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Close_codes +// http://docs.oracle.com/javaee/7/api/javax/websocket/CloseReason.CloseCodes.html +wsCloseErrorCodes = { + 1002: 'The endpoint is terminating the connection due to a protocol error. ' + + '(CLOSE_PROTOCOL_ERROR)', + 1003: 'The connection is being terminated because the endpoint received data of ' + + 'a type it cannot accept (for example, a text-only endpoint received binary data). ' + + '(CLOSE_UNSUPPORTED)', + 1004: 'The endpoint is terminating the connection because a data frame was received ' + + 'that is too large. (CLOSE_TOO_LARGE)', + 1005: 'Indicates that no status code was provided even though one was expected. ' + + '(CLOSE_NO_STATUS)', + 1006: 'Used to indicate that a connection was closed abnormally (that is, with no ' + + 'close frame being sent) when a status code is expected. (CLOSE_ABNORMAL)', + 1007: 'Indicates that an endpoint is terminating the connection because it has received ' + + 'data within a message that was not consistent with the type of the message (e.g., ' + + 'non-UTF-8 [RFC3629] data within a text message)', + 1008: 'Indicates that an endpoint is terminating the connection because it has received a ' + + 'message that violates its policy. This is a generic status code that can be returned ' + + 'when there is no other more suitable status code (e.g., 1003 or 1009) or if there is a ' + + 'need to hide specific details about the policy', + 1009: 'Indicates that an endpoint is terminating the connection because it has received a ' + + 'message that is too big for it to process', + 1011: 'Indicates that a server is terminating the connection because it encountered an ' + + 'unexpected condition that prevented it from fulfilling the request', + + // .... codes in the 4000-4999 range are available for use by applications. + 4001: 'Connectivity loss was detected as it was too long since the socket received the ' + + 'last PONG message' +}; + +OT.Rumor.SocketError = function(code, message) { + this.code = code; + this.message = message; +}; + +// The NativeSocket bit is purely to make testing simpler, it defaults to WebSocket +// so in normal operation you would omit it. +OT.Rumor.Socket = function(messagingURL, notifyDisconnectAddress, NativeSocket) { + + var states = ['disconnected', 'error', 'connected', 'connecting', 'disconnecting'], + webSocket, + id, + onOpen, + onError, + onClose, + onMessage, + connectCallback, + connectTimeout, + lastMessageTimestamp, // The timestamp of the last message received + keepAliveTimer; // Timer for the connectivity checks + + + //// Private API + var stateChanged = function(newState) { + switch (newState) { + case 'disconnected': + case 'error': + webSocket = null; + if (onClose) { + var error; + if(hasLostConnectivity()) { + error = new Error(wsCloseErrorCodes[4001]); + error.code = 4001; + } + onClose(error); } - } - catch(e) { - error = {code: 400, reason: 'The data field was not valid JSON'}; - } + break; + } + }, + + setState = OT.$.statable(this, states, 'disconnected', stateChanged), + + validateCallback = function validateCallback (name, callback) { + if (callback === null || !OT.$.isFunction(callback) ) { + throw new Error('The Rumor.Socket ' + name + + ' callback must be a valid function or null'); + } + }, + + error = OT.$.bind(function error (errorMessage) { + OT.error('Rumor.Socket: ' + errorMessage); + + var socketError = new OT.Rumor.SocketError(null, errorMessage || 'Unknown Socket Error'); + + if (connectTimeout) clearTimeout(connectTimeout); + + setState('error'); + + if (this.previousState === 'connecting' && connectCallback) { + connectCallback(socketError, void 0); + connectCallback = null; } - return error; + if (onError) onError(socketError); + }, this), + + hasLostConnectivity = function hasLostConnectivity () { + if (!lastMessageTimestamp) return false; + + return (OT.$.now() - lastMessageTimestamp) >= WEB_SOCKET_CONNECTIVITY_TIMEOUT; + }, + + sendKeepAlive = OT.$.bind(function() { + if (!this.is('connected')) return; + + if ( hasLostConnectivity() ) { + webSocketDisconnected({code: 4001}); + } + else { + webSocket.send(OT.Rumor.Message.Ping()); + keepAliveTimer = setTimeout(sendKeepAlive, WEB_SOCKET_KEEP_ALIVE_INTERVAL); + } + }, this), + + // Returns true if we think the DOM has been unloaded + // It detects this by looking for the OT global, which + // should always exist until the DOM is cleaned up. + isDOMUnloaded = function isDOMUnloaded () { + return !window.OT; }; - this.toRaptorMessage = function() { - var to = this.to; - - if (to && typeof(to) !== 'string') { - to = to.id; - } - - return OT.Raptor.Message.signals.create(OT.APIKEY, sessionId, to, this.type, this.data); - }; - - this.toHash = function() { - return options; - }; - - - this.error = null; - - if (options) { - if (options.hasOwnProperty('data')) { - this.data = OT.$.clone(options.data); - this.error = validateData(this.data); - } - - if (options.hasOwnProperty('to')) { - this.to = options.to; - - if (!this.error) { - this.error = validateTo(this.to); - } - } - - if (options.hasOwnProperty('type')) { - if (!this.error) { - this.error = validateType(options.type); - } - this.type = options.type; - } - } - - this.valid = this.error === null; - }; - -}(this)); -!(function() { - - function SignalError(code, reason) { - this.code = code; - this.reason = reason; - } - - // The Dispatcher bit is purely to make testing simpler, it defaults to a new OT.Raptor.Dispatcher - // so in normal operation you would omit it. - OT.Raptor.Socket = function(widgetId, messagingSocketUrl, symphonyUrl, dispatcher) { - var _states = ['disconnected', 'connecting', 'connected', 'error', 'disconnecting'], - _sessionId, - _token, - _rumor, - _dispatcher, - _completion; - - - //// Private API - var setState = OT.$.statable(this, _states, 'disconnected'), - - onConnectComplete = function onConnectComplete(error) { - if (error) { - setState('error'); - } - else { - setState('connected'); - } - - _completion.apply(null, arguments); - }, - - onClose = OT.$.bind(function onClose (err) { - var reason = this.is('disconnecting') ? 'clientDisconnected' : 'networkDisconnected'; - - if(err && err.code === 4001) { - reason = 'networkTimedout'; - } - - setState('disconnected'); - - _dispatcher.onClose(reason); - }, this), - - onError = function onError () {}; - // @todo what does having an error mean? Are they always fatal? Are we disconnected now? - - - //// Public API - - this.connect = function (token, sessionInfo, completion) { - if (!this.is('disconnected', 'error')) { - OT.warn('Cannot connect the Raptor Socket as it is currently connected. You should ' + - 'disconnect first.'); - return; - } - - setState('connecting'); - _sessionId = sessionInfo.sessionId; - _token = token; - _completion = completion; - - var connectionId = OT.$.uuid(), - rumorChannel = '/v2/partner/' + OT.APIKEY + '/session/' + _sessionId; - - _rumor = new OT.Rumor.Socket(messagingSocketUrl, symphonyUrl); - _rumor.onClose(onClose); - _rumor.onMessage(OT.$.bind(_dispatcher.dispatch, _dispatcher)); - - _rumor.connect(connectionId, OT.$.bind(function(error) { - if (error) { - error.message = 'WebSocketConnection:' + error.code + ':' + error.message; - onConnectComplete(error); + //// Private Event Handlers + var webSocketConnected = OT.$.bind(function webSocketConnected () { + if (connectTimeout) clearTimeout(connectTimeout); + if (this.isNot('connecting')) { + OT.debug('webSocketConnected reached in state other than connecting'); return; } - // we do this here to avoid getting connect errors twice - _rumor.onError(onError); + // Connect to Rumor by registering our connection id and the + // app server address to notify if we disconnect. + // + // We don't need to wait for a reply to this message. + webSocket.send(OT.Rumor.Message.Connect(id, notifyDisconnectAddress)); - OT.debug('Raptor Socket connected. Subscribing to ' + - rumorChannel + ' on ' + messagingSocketUrl); + setState('connected'); + if (connectCallback) { + connectCallback(void 0, id); + connectCallback = null; + } - _rumor.subscribe([rumorChannel]); + if (onOpen) onOpen(id); - //connect to session - var connectMessage = OT.Raptor.Message.connections.create(OT.APIKEY, - _sessionId, _rumor.id()); - this.publish(connectMessage, {'X-TB-TOKEN-AUTH': _token}, OT.$.bind(function(error) { - if (error) { - error.message = 'ConnectToSession:' + error.code + - ':Received error response to connection create message.'; - onConnectComplete(error); - return; + keepAliveTimer = setTimeout(function() { + lastMessageTimestamp = OT.$.now(); + sendKeepAlive(); + }, WEB_SOCKET_KEEP_ALIVE_INTERVAL); + }, this), + + webSocketConnectTimedOut = function webSocketConnectTimedOut () { + var webSocketWas = webSocket; + error('Timed out while waiting for the Rumor socket to connect.'); + // This will prevent a socket eventually connecting + // But call it _after_ the error just in case any of + // the callbacks fire synchronously, breaking the error + // handling code. + try { + webSocketWas.close(); + } catch(x) {} + }, + + webSocketError = function webSocketError () {}, + // var errorMessage = 'Unknown Socket Error'; + // @fixme We MUST be able to do better than this! + + // All errors seem to result in disconnecting the socket, the close event + // has a close reason and code which gives some error context. This, + // combined with the fact that the errorEvent argument contains no + // error info at all, means we'll delay triggering the error handlers + // until the socket is closed. + // error(errorMessage); + + webSocketDisconnected = OT.$.bind(function webSocketDisconnected (closeEvent) { + if (connectTimeout) clearTimeout(connectTimeout); + if (keepAliveTimer) clearTimeout(keepAliveTimer); + + if (isDOMUnloaded()) { + // Sometimes we receive the web socket close event after + // the DOM has already been partially or fully unloaded + // if that's the case here then it's not really safe, or + // desirable, to continue. + return; + } + + if (closeEvent.code !== 1000 && closeEvent.code !== 1001) { + var reason = closeEvent.reason || closeEvent.message; + if (!reason && wsCloseErrorCodes.hasOwnProperty(closeEvent.code)) { + reason = wsCloseErrorCodes[closeEvent.code]; } - this.publish( OT.Raptor.Message.sessions.get(OT.APIKEY, _sessionId), - function (error) { - if (error) { - error.message = 'GetSessionState:' + error.code + - ':Received error response to session read'; - } - onConnectComplete.apply(null, arguments); - }); - }, this)); - }, this)); - }; - - - this.disconnect = function (drainSocketBuffer) { - if (this.is('disconnected')) return; - - setState('disconnecting'); - _rumor.disconnect(drainSocketBuffer); - }; - - // Publishs +message+ to the Symphony app server. - // - // The completion handler is optional, as is the headers - // dict, but if you provide the completion handler it must - // be the last argument. - // - this.publish = function (message, headers, completion) { - if (_rumor.isNot('connected')) { - OT.error('OT.Raptor.Socket: cannot publish until the socket is connected.' + message); - return; - } - - var transactionId = OT.$.uuid(), - _headers = {}, - _completion; - - // Work out if which of the optional arguments (headers, completion) - // have been provided. - if (headers) { - if (OT.$.isFunction(headers)) { - _headers = {}; - _completion = headers; - } - else { - _headers = headers; - } - } - if (!_completion && completion && OT.$.isFunction(completion)) _completion = completion; - - - if (_completion) _dispatcher.registerCallback(transactionId, _completion); - - OT.debug('OT.Raptor.Socket Publish (ID:' + transactionId + ') '); - OT.debug(message); - - _rumor.publish([symphonyUrl], message, OT.$.extend(_headers, { - 'Content-Type': 'application/x-raptor+v2', - 'TRANSACTION-ID': transactionId, - 'X-TB-FROM-ADDRESS': _rumor.id() - })); - - return transactionId; - }; - - // Register a new stream against _sessionId - this.streamCreate = function(name, orientation, encodedWidth, encodedHeight, - hasAudio, hasVideo, frameRate, minBitrate, maxBitrate, completion) { - var streamId = OT.$.uuid(), - message = OT.Raptor.Message.streams.create( OT.APIKEY, - _sessionId, - streamId, - name, - orientation, - encodedWidth, - encodedHeight, - hasAudio, - hasVideo, - frameRate, - minBitrate, - maxBitrate); - - this.publish(message, function(error, message) { - completion(error, streamId, message); - }); - }; - - this.streamDestroy = function(streamId) { - this.publish( OT.Raptor.Message.streams.destroy(OT.APIKEY, _sessionId, streamId) ); - }; - - this.streamChannelUpdate = function(streamId, channelId, attributes) { - this.publish( OT.Raptor.Message.streamChannels.update(OT.APIKEY, _sessionId, - streamId, channelId, attributes) ); - }; - - this.subscriberCreate = function(streamId, subscriberId, channelsToSubscribeTo, completion) { - this.publish( OT.Raptor.Message.subscribers.create(OT.APIKEY, _sessionId, - streamId, subscriberId, _rumor.id(), channelsToSubscribeTo), completion ); - }; - - this.subscriberDestroy = function(streamId, subscriberId) { - this.publish( OT.Raptor.Message.subscribers.destroy(OT.APIKEY, _sessionId, - streamId, subscriberId) ); - }; - - this.subscriberUpdate = function(streamId, subscriberId, attributes) { - this.publish( OT.Raptor.Message.subscribers.update(OT.APIKEY, _sessionId, - streamId, subscriberId, attributes) ); - }; - - this.subscriberChannelUpdate = function(streamId, subscriberId, channelId, attributes) { - this.publish( OT.Raptor.Message.subscriberChannels.update(OT.APIKEY, _sessionId, - streamId, subscriberId, channelId, attributes) ); - }; - - this.forceDisconnect = function(connectionIdToDisconnect, completion) { - this.publish( OT.Raptor.Message.connections.destroy(OT.APIKEY, _sessionId, - connectionIdToDisconnect), completion ); - }; - - this.forceUnpublish = function(streamIdToUnpublish, completion) { - this.publish( OT.Raptor.Message.streams.destroy(OT.APIKEY, _sessionId, - streamIdToUnpublish), completion ); - }; - - this.jsepCandidate = function(streamId, candidate) { - this.publish( - OT.Raptor.Message.streams.candidate(OT.APIKEY, _sessionId, streamId, candidate) - ); - }; - - this.jsepCandidateP2p = function(streamId, subscriberId, candidate) { - this.publish( - OT.Raptor.Message.subscribers.candidate(OT.APIKEY, _sessionId, streamId, - subscriberId, candidate) - ); - }; - - this.jsepOffer = function(streamId, offerSdp) { - this.publish( OT.Raptor.Message.streams.offer(OT.APIKEY, _sessionId, streamId, offerSdp) ); - }; - - this.jsepOfferP2p = function(streamId, subscriberId, offerSdp) { - this.publish( OT.Raptor.Message.subscribers.offer(OT.APIKEY, _sessionId, streamId, - subscriberId, offerSdp) ); - }; - - this.jsepAnswer = function(streamId, answerSdp) { - this.publish( OT.Raptor.Message.streams.answer(OT.APIKEY, _sessionId, streamId, answerSdp) ); - }; - - this.jsepAnswerP2p = function(streamId, subscriberId, answerSdp) { - this.publish( OT.Raptor.Message.subscribers.answer(OT.APIKEY, _sessionId, streamId, - subscriberId, answerSdp) ); - }; - - this.signal = function(options, completion) { - var signal = new OT.Signal(_sessionId, _rumor.id(), options || {}); - - if (!signal.valid) { - if (completion && OT.$.isFunction(completion)) { - completion( new SignalError(signal.error.code, signal.error.reason), signal.toHash() ); + error('Rumor Socket Disconnected: ' + reason); } - return; - } + if (this.isNot('error')) setState('disconnected'); + }, this), - this.publish( signal.toRaptorMessage(), function(err) { - var error; - if (err) error = new SignalError(err.code, err.message); + webSocketReceivedMessage = function webSocketReceivedMessage (msg) { + lastMessageTimestamp = OT.$.now(); - if (completion && OT.$.isFunction(completion)) completion(error, signal.toHash()); - }); - }; + if (onMessage) { + if (msg.type !== OT.Rumor.MessageType.PONG) { + onMessage(msg); + } + } + }; - this.id = function() { - return _rumor && _rumor.id(); - }; - if(dispatcher == null) { - dispatcher = new OT.Raptor.Dispatcher(); - } - _dispatcher = dispatcher; + //// Public API + + this.publish = function (topics, message, headers) { + webSocket.send(OT.Rumor.Message.Publish(topics, message, headers)); }; -}(this)); + this.subscribe = function(topics) { + webSocket.send(OT.Rumor.Message.Subscribe(topics)); + }; + + this.unsubscribe = function(topics) { + webSocket.send(OT.Rumor.Message.Unsubscribe(topics)); + }; + + this.connect = function (connectionId, complete) { + if (this.is('connecting', 'connected')) { + complete(new OT.Rumor.SocketError(null, + 'Rumor.Socket cannot connect when it is already connecting or connected.')); + return; + } + + id = connectionId; + connectCallback = complete; + + setState('connecting'); + + var TheWebSocket = NativeSocket || window.WebSocket; + + var events = { + onOpen: webSocketConnected, + onClose: webSocketDisconnected, + onError: webSocketError, + onMessage: webSocketReceivedMessage + }; + + try { + if(typeof TheWebSocket !== 'undefined') { + webSocket = new OT.Rumor.NativeSocket(TheWebSocket, messagingURL, events); + } else { + webSocket = new OT.Rumor.PluginSocket(messagingURL, events); + } + + connectTimeout = setTimeout(webSocketConnectTimedOut, OT.Rumor.Socket.CONNECT_TIMEOUT); + } + catch(e) { + OT.error(e); + + // @todo add an actual error message + error('Could not connect to the Rumor socket, possibly because of a blocked port.'); + } + }; + + this.disconnect = function(drainSocketBuffer) { + if (connectTimeout) clearTimeout(connectTimeout); + if (keepAliveTimer) clearTimeout(keepAliveTimer); + + if (!webSocket) { + if (this.isNot('error')) setState('disconnected'); + return; + } + + if (webSocket.isClosed()) { + if (this.isNot('error')) setState('disconnected'); + } + else { + if (this.is('connected')) { + // Look! We are nice to the rumor server ;-) + webSocket.send(OT.Rumor.Message.Disconnect()); + } + + // Wait until the socket is ready to close + webSocket.close(drainSocketBuffer); + } + }; + + + + OT.$.defineProperties(this, { + id: { + get: function() { return id; } + }, + + onOpen: { + set: function(callback) { + validateCallback('onOpen', callback); + onOpen = callback; + }, + + get: function() { return onOpen; } + }, + + onError: { + set: function(callback) { + validateCallback('onError', callback); + onError = callback; + }, + + get: function() { return onError; } + }, + + onClose: { + set: function(callback) { + validateCallback('onClose', callback); + onClose = callback; + }, + + get: function() { return onClose; } + }, + + onMessage: { + set: function(callback) { + validateCallback('onMessage', callback); + onMessage = callback; + }, + + get: function() { return onMessage; } + } + }); +}; + +// The number of ms to wait for the websocket to connect +OT.Rumor.Socket.CONNECT_TIMEOUT = 15000; + + +// tb_require('../../../helpers/helpers.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ + +// Rumor Messaging for JS +// +// https://tbwiki.tokbox.com/index.php/Raptor_Messages_(Sent_as_a_RumorMessage_payload_in_JSON) +// +// @todo Raptor { +// Look at disconnection cleanup: i.e. subscriber + publisher cleanup +// Add error codes for all the error cases +// Write unit tests for SessionInfo +// Write unit tests for Session +// Make use of the new DestroyedEvent +// Remove dependency on OT.properties +// OT.Capabilities must be part of the Raptor namespace +// Add Dependability commands +// Think about noConflict, or whether we should just use the OT namespace +// Think about how to expose OT.publishers, OT.subscribers, and OT.sessions if messaging was +// being included as a component +// Another solution to the problem of having publishers/subscribers/etc would be to make +// Raptor Socket a separate component from Dispatch (dispatch being more business logic) +// Look at the coupling of OT.sessions to OT.Raptor.Socket +// } +// +// @todo Raptor Docs { +// Document payload formats for incoming messages (what are the payloads for +// STREAM CREATED/MODIFIED for example) +// Document how keepalives work +// Document all the Raptor actions and types +// Document the session connect flow (including error cases) +// } + +OT.Raptor = { + Actions: { + //General + CONNECT: 100, + CREATE: 101, + UPDATE: 102, + DELETE: 103, + STATE: 104, + + //Moderation + FORCE_DISCONNECT: 105, + FORCE_UNPUBLISH: 106, + SIGNAL: 107, + + //Archives + CREATE_ARCHIVE: 108, + CLOSE_ARCHIVE: 109, + START_RECORDING_SESSION: 110, + STOP_RECORDING_SESSION: 111, + START_RECORDING_STREAM: 112, + STOP_RECORDING_STREAM: 113, + LOAD_ARCHIVE: 114, + START_PLAYBACK: 115, + STOP_PLAYBACK: 116, + + //AppState + APPSTATE_PUT: 117, + APPSTATE_DELETE: 118, + + // JSEP + OFFER: 119, + ANSWER: 120, + PRANSWER: 121, + CANDIDATE: 122, + SUBSCRIBE: 123, + UNSUBSCRIBE: 124, + QUERY: 125, + SDP_ANSWER: 126, + + //KeepAlive + PONG: 127, + REGISTER: 128, //Used for registering streams. + + QUALITY_CHANGED: 129 + }, + + Types: { + //RPC + RPC_REQUEST: 100, + RPC_RESPONSE: 101, + + //EVENT + STREAM: 102, + ARCHIVE: 103, + CONNECTION: 104, + APPSTATE: 105, + CONNECTIONCOUNT: 106, + MODERATION: 107, + SIGNAL: 108, + SUBSCRIBER: 110, + + //JSEP Protocol + JSEP: 109 + } +}; + +// tb_require('../../../helpers/helpers.js') +// tb_require('./raptor.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ + +OT.Raptor.serializeMessage = function (message) { + return JSON.stringify(message); +}; + + +// Deserialising a Raptor message mainly means doing a JSON.parse on it. +// We do decorate the final message with a few extra helper properies though. +// +// These include: +// * typeName: A human readable version of the Raptor type. E.g. STREAM instead of 102 +// * actionName: A human readable version of the Raptor action. E.g. CREATE instead of 101 +// * signature: typeName and actionName combined. This is mainly for debugging. E.g. A type +// of 102 and an action of 101 would result in a signature of "STREAM:CREATE" +// +OT.Raptor.deserializeMessage = function (msg) { + if (msg.length === 0) return {}; + + var message = JSON.parse(msg), + bits = message.uri.substr(1).split('/'); + + // Remove the Raptor protocol version + bits.shift(); + if (bits[bits.length-1] === '') bits.pop(); + + message.params = {}; + for (var i=0, numBits=bits.length ; i 6) { + message.resource = bits[bits.length-4] + '_' + bits[bits.length-2]; + } else { + message.resource = bits[bits.length-2]; + } + } + else { + if (bits[bits.length-1] === 'channel' && bits.length > 5) { + message.resource = bits[bits.length-3] + '_' + bits[bits.length-1]; + } else { + message.resource = bits[bits.length-1]; + } + } + + message.signature = message.resource + '#' + message.method; + return message; +}; + +OT.Raptor.unboxFromRumorMessage = function (rumorMessage) { + var message = OT.Raptor.deserializeMessage(rumorMessage.data); + message.transactionId = rumorMessage.transactionId; + message.fromAddress = rumorMessage.headers['X-TB-FROM-ADDRESS']; + + return message; +}; + +OT.Raptor.parseIceServers = function (message) { + try { + return JSON.parse(message.data).content.iceServers; + } catch (e) { + return []; + } +}; + +OT.Raptor.Message = {}; + + +OT.Raptor.Message.offer = function (uri, offerSdp) { + return OT.Raptor.serializeMessage({ + method: 'offer', + uri: uri, + content: { + sdp: offerSdp + } + }); +}; + + +OT.Raptor.Message.connections = {}; + +OT.Raptor.Message.connections.create = function (apiKey, sessionId, connectionId) { + return OT.Raptor.serializeMessage({ + method: 'create', + uri: '/v2/partner/' + apiKey + '/session/' + sessionId + '/connection/' + connectionId, + content: { + userAgent: OT.$.env.userAgent + } + }); +}; + +OT.Raptor.Message.connections.destroy = function (apiKey, sessionId, connectionId) { + return OT.Raptor.serializeMessage({ + method: 'delete', + uri: '/v2/partner/' + apiKey + '/session/' + sessionId + '/connection/' + connectionId, + content: {} + }); +}; + + +OT.Raptor.Message.sessions = {}; + +OT.Raptor.Message.sessions.get = function (apiKey, sessionId) { + return OT.Raptor.serializeMessage({ + method: 'read', + uri: '/v2/partner/' + apiKey + '/session/' + sessionId, + content: {} + }); +}; + + +OT.Raptor.Message.streams = {}; + +OT.Raptor.Message.streams.get = function (apiKey, sessionId, streamId) { + return OT.Raptor.serializeMessage({ + method: 'read', + uri: '/v2/partner/' + apiKey + '/session/' + sessionId + '/stream/' + streamId, + content: {} + }); +}; + +OT.Raptor.Message.streams.channelFromOTChannel = function(channel) { + var raptorChannel = { + id: channel.id, + type: channel.type, + active: channel.active + }; + + if (channel.type === 'video') { + raptorChannel.width = channel.width; + raptorChannel.height = channel.height; + raptorChannel.orientation = channel.orientation; + raptorChannel.frameRate = channel.frameRate; + if (channel.source !== 'default') { + raptorChannel.source = channel.source; + } + raptorChannel.fitMode = channel.fitMode; + } + + return raptorChannel; +}; + +OT.Raptor.Message.streams.create = function (apiKey, sessionId, streamId, name, + audioFallbackEnabled, channels, minBitrate, maxBitrate) { + var messageContent = { + id: streamId, + name: name, + audioFallbackEnabled: audioFallbackEnabled, + channel: OT.$.map(channels, function(channel) { + return OT.Raptor.Message.streams.channelFromOTChannel(channel); + }) + }; + + if (minBitrate) messageContent.minBitrate = minBitrate; + if (maxBitrate) messageContent.maxBitrate = maxBitrate; + + return OT.Raptor.serializeMessage({ + method: 'create', + uri: '/v2/partner/' + apiKey + '/session/' + sessionId + '/stream/' + streamId, + content: messageContent + }); +}; + +OT.Raptor.Message.streams.destroy = function (apiKey, sessionId, streamId) { + return OT.Raptor.serializeMessage({ + method: 'delete', + uri: '/v2/partner/' + apiKey + '/session/' + sessionId + '/stream/' + streamId, + content: {} + }); +}; + + +OT.Raptor.Message.streams.answer = function (apiKey, sessionId, streamId, answerSdp) { + return OT.Raptor.serializeMessage({ + method: 'answer', + uri: '/v2/partner/' + apiKey + '/session/' + sessionId + '/stream/' + streamId, + content: { + sdp: answerSdp + } + }); +}; + +OT.Raptor.Message.streams.candidate = function (apiKey, sessionId, streamId, candidate) { + return OT.Raptor.serializeMessage({ + method: 'candidate', + uri: '/v2/partner/' + apiKey + '/session/' + sessionId + '/stream/' + streamId, + content: candidate + }); +}; + +OT.Raptor.Message.streamChannels = {}; +OT.Raptor.Message.streamChannels.update = + function (apiKey, sessionId, streamId, channelId, attributes) { + return OT.Raptor.serializeMessage({ + method: 'update', + uri: '/v2/partner/' + apiKey + '/session/' + sessionId + '/stream/' + + streamId + '/channel/' + channelId, + content: attributes + }); +}; + + +OT.Raptor.Message.subscribers = {}; + +OT.Raptor.Message.subscribers.create = + function (apiKey, sessionId, streamId, subscriberId, connectionId, channelsToSubscribeTo) { + var content = { + id: subscriberId, + connection: connectionId, + keyManagementMethod: OT.$.supportedCryptoScheme(), + bundleSupport: OT.$.hasCapabilities('bundle'), + rtcpMuxSupport: OT.$.hasCapabilities('RTCPMux') + }; + if (channelsToSubscribeTo) content.channel = channelsToSubscribeTo; + + return OT.Raptor.serializeMessage({ + method: 'create', + uri: '/v2/partner/' + apiKey + '/session/' + sessionId + + '/stream/' + streamId + '/subscriber/' + subscriberId, + content: content + }); +}; + +OT.Raptor.Message.subscribers.destroy = function (apiKey, sessionId, streamId, subscriberId) { + return OT.Raptor.serializeMessage({ + method: 'delete', + uri: '/v2/partner/' + apiKey + '/session/' + sessionId + + '/stream/' + streamId + '/subscriber/' + subscriberId, + content: {} + }); +}; + +OT.Raptor.Message.subscribers.update = + function (apiKey, sessionId, streamId, subscriberId, attributes) { + return OT.Raptor.serializeMessage({ + method: 'update', + uri: '/v2/partner/' + apiKey + '/session/' + sessionId + + '/stream/' + streamId + '/subscriber/' + subscriberId, + content: attributes + }); +}; + + +OT.Raptor.Message.subscribers.candidate = + function (apiKey, sessionId, streamId, subscriberId, candidate) { + return OT.Raptor.serializeMessage({ + method: 'candidate', + uri: '/v2/partner/' + apiKey + '/session/' + sessionId + + '/stream/' + streamId + '/subscriber/' + subscriberId, + content: candidate + }); +}; + + +OT.Raptor.Message.subscribers.answer = + function (apiKey, sessionId, streamId, subscriberId, answerSdp) { + return OT.Raptor.serializeMessage({ + method: 'answer', + uri: '/v2/partner/' + apiKey + '/session/' + sessionId + + '/stream/' + streamId + '/subscriber/' + subscriberId, + content: { + sdp: answerSdp + } + }); +}; + + +OT.Raptor.Message.subscriberChannels = {}; + +OT.Raptor.Message.subscriberChannels.update = + function (apiKey, sessionId, streamId, subscriberId, channelId, attributes) { + return OT.Raptor.serializeMessage({ + method: 'update', + uri: '/v2/partner/' + apiKey + '/session/' + sessionId + + '/stream/' + streamId + '/subscriber/' + subscriberId + '/channel/' + channelId, + content: attributes + }); +}; + + +OT.Raptor.Message.signals = {}; + +OT.Raptor.Message.signals.create = function (apiKey, sessionId, toAddress, type, data) { + var content = {}; + if (type !== void 0) content.type = type; + if (data !== void 0) content.data = data; + + return OT.Raptor.serializeMessage({ + method: 'signal', + uri: '/v2/partner/' + apiKey + '/session/' + sessionId + + (toAddress !== void 0 ? '/connection/' + toAddress : '') + '/signal/' + OT.$.uuid(), + content: content + }); +}; + +// tb_require('../../../helpers/helpers.js') +// tb_require('./message.js') + + !(function() { - /*global EventEmitter, util*/ + /* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ + /* global OT, EventEmitter, util */ // Connect error codes and reasons that Raptor can return. var connectErrorReasons; @@ -13064,7 +8325,7 @@ waitForDomReady(); OT.Raptor.Dispatcher = function () { - if(OT.isNodeModule) { + if(OT.$.env.name === 'Node') { EventEmitter.call(this); } else { OT.$.eventing(this, true); @@ -13074,7 +8335,7 @@ waitForDomReady(); this.callbacks = {}; }; - if(OT.isNodeModule) { + if(OT.$.env.name === 'Node') { util.inherits(OT.Raptor.Dispatcher, EventEmitter); } @@ -13322,7 +8583,7 @@ waitForDomReady(); case 'created': this.emit('archive#created', message.content); break; - + case 'updated': this.emit('archive#updated', message.params.archive, message.content); break; @@ -13330,12 +8591,21 @@ waitForDomReady(); }; }(this)); + +// tb_require('../../../helpers/helpers.js') +// tb_require('./message.js') +// tb_require('./dispatch.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ + (function(window) { // @todo hide these - OT.publishers = new OT.Collection('guid'); // Publishers are id'd by their guid - OT.subscribers = new OT.Collection('widgetId'); // Subscribers are id'd by their widgetId - OT.sessions = new OT.Collection(); + OT.publishers = new OT.$.Collection('guid'); // Publishers are id'd by their guid + OT.subscribers = new OT.$.Collection('widgetId'); // Subscribers are id'd by their widgetId + OT.sessions = new OT.$.Collection(); function parseStream(dict, session) { var channel = dict.channel.map(function(channel) { @@ -13376,20 +8646,78 @@ waitForDomReady(); return archive; } - var sessionRead, - sessionReadQueue = [], - // streams for which corresponding connectionCreated events have not been dispatched: - unconnectedStreams = {}; + var DelayedEventQueue = function DelayedEventQueue (eventDispatcher) { + var queue = []; - function sessionReadQueuePush(type, args) { - var triggerArgs = ['signal']; - triggerArgs.push.apply(triggerArgs, args); - sessionReadQueue.push(triggerArgs); - } + this.enqueue = function enqueue (/* arg1, arg2, ..., argN */) { + queue.push( Array.prototype.slice.call(arguments) ); + }; + + this.triggerAll = function triggerAll () { + var event; + + // Array.prototype.shift is actually pretty inefficient for longer Arrays, + // this is because after the first element is removed it reshuffles every + // remaining element up one (1). This involves way too many allocations and + // deallocations as the queue size increases. + // + // A more efficient version could be written by keeping an index to the current + // 'first' element in the Array and increasing that by one whenever an element + // is removed. The elements that are infront of the index have been 'deleted'. + // Periodically the front of the Array could be spliced off to reclaim the space. + // + // 1. http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.9 + // + // + // TLDR: Array.prototype.shift is O(n), where n is the array length, + // instead of the expected O(1). You can implement your own shift that runs + // in amortised constant time. + // + // @todo benchmark and see if we should actually care about shift's performance + // for our common queue sizes. + // + while( (event = queue.shift()) ) { + eventDispatcher.trigger.apply(eventDispatcher, event); + } + }; + }; + + var DelayedSessionEvents = function(dispatcher) { + var eventQueues = {}; + + this.enqueue = function enqueue (/* key, arg1, arg2, ..., argN */) { + var key = arguments[0]; + var eventArgs = Array.prototype.slice.call(arguments, 1); + if (!eventQueues[key]) { + eventQueues[key] = new DelayedEventQueue(dispatcher); + } + eventQueues[key].enqueue.apply(eventQueues[key], eventArgs); + }; + + this.triggerConnectionCreated = function triggerConnectionCreated (connection) { + if (eventQueues['connectionCreated' + connection.id]) { + eventQueues['connectionCreated' + connection.id].triggerAll(); + } + }; + + this.triggerSessionConnected = function triggerSessionConnected (connections) { + if (eventQueues.sessionConnected) { + eventQueues.sessionConnected.triggerAll(); + } + + OT.$.forEach(connections, function(connection) { + this.triggerConnectionCreated(connection); + }); + }; + }; + + var unconnectedStreams = {}; window.OT.SessionDispatcher = function(session) { - var dispatcher = new OT.Raptor.Dispatcher(); + var dispatcher = new OT.Raptor.Dispatcher(), + sessionStateReceived = false, + delayedSessionEvents = new DelayedSessionEvents(dispatcher); dispatcher.on('close', function(reason) { @@ -13406,8 +8734,36 @@ waitForDomReady(); } connection.destroy( reason ); - }); + + // This method adds connections to the session both on a connection#created and + // on a session#read. In the case of session#read sessionRead is set to true and + // we include our own connection. + var addConnection = function (connection, sessionRead) { + connection = OT.Connection.fromHash(connection); + if (sessionRead || session.connection && connection.id !== session.connection.id) { + session.connections.add( connection ); + delayedSessionEvents.triggerConnectionCreated(connection); + } + + OT.$.forEach(OT.$.keys(unconnectedStreams), function(streamId) { + var stream = unconnectedStreams[streamId]; + if (stream && connection.id === stream.connection.id) { + // dispatch streamCreated event now that the connectionCreated has been dispatched + parseAndAddStreamToSession(stream, session); + delete unconnectedStreams[stream.id]; + + var payload = { + debug: sessionRead ? 'connection came in session#read' : + 'connection came in connection#created', + streamId : stream.id, + }; + session.logEvent('streamCreated', 'warning', payload); + } + }); + + return connection; + }; dispatcher.on('session#read', function(content, transactionId) { @@ -13419,9 +8775,8 @@ waitForDomReady(); state.archives = []; OT.$.forEach(content.connection, function(connectionParams) { - connection = OT.Connection.fromHash(connectionParams); + connection = addConnection(connectionParams, true); state.connections.push(connection); - session.connections.add(connection); }); OT.$.forEach(content.stream, function(streamParams) { @@ -13436,28 +8791,12 @@ waitForDomReady(); dispatcher.triggerCallback(transactionId, null, state); - sessionRead = true; - for (var i = 0; i < sessionReadQueue.length; ++i) { - dispatcher.trigger.apply(dispatcher, sessionReadQueue[i]); - } - sessionReadQueue = []; - + sessionStateReceived = true; + delayedSessionEvents.triggerSessionConnected(session.connections); }); dispatcher.on('connection#created', function(connection) { - connection = OT.Connection.fromHash(connection); - if (session.connection && connection.id !== session.connection.id) { - session.connections.add( connection ); - } - - OT.$.forEach(OT.$.keys(unconnectedStreams), function(streamId) { - var stream = unconnectedStreams[streamId]; - if (stream && connection.id === stream.connection.id) { - // dispatch streamCreated event now that the connectionCreated has been dispatched - parseAndAddStreamToSession(stream, session); - delete unconnectedStreams[stream.id]; - } - }); + addConnection(connection); }); dispatcher.on('connection#deleted', function(connection, reason) { @@ -13471,6 +8810,12 @@ waitForDomReady(); stream = parseAndAddStreamToSession(stream, session); } else { unconnectedStreams[stream.id] = stream; + + var payload = { + type : 'eventOrderError -- streamCreated event before connectionCreated', + streamId : stream.id, + }; + session.logEvent('streamCreated', 'warning', payload); } if (stream.publisher) { @@ -13668,12 +9013,20 @@ waitForDomReady(); }); dispatcher.on('signal', function(fromAddress, signalType, data) { - if (sessionRead) { - var fromConnection = session.connections.get(fromAddress); - session._.dispatchSignal(fromConnection, signalType, data); + var fromConnection = session.connections.get(fromAddress); + if (session.connection && fromAddress === session.connection.connectionId) { + if (sessionStateReceived) { + session._.dispatchSignal(fromConnection, signalType, data); + } else { + delayedSessionEvents.enqueue('sessionConnected', + 'signal', fromAddress, signalType, data); + } } else { - if (!sessionRead) { - sessionReadQueuePush('signal', arguments); + if (session.connections.get(fromAddress)) { + session._.dispatchSignal(fromConnection, signalType, data); + } else { + delayedSessionEvents.enqueue('connectionCreated' + fromAddress, + 'signal', fromAddress, signalType, data); } } }); @@ -13700,135 +9053,8106 @@ waitForDomReady(); }; })(window); -!(function() { - // Helper to synchronise several startup tasks and then dispatch a unified - // 'envLoaded' event. - // - // This depends on: - // * OT - // * OT.Config - // - function EnvironmentLoader() { - var _configReady = false, +// tb_require('../../helpers/helpers.js') - // If the plugin is installed, then we should wait for it to - // be ready as well. - _pluginSupported = TBPlugin.isSupported(), - _pluginLoadAttemptComplete = _pluginSupported ? TBPlugin.isReady() : true, +/* global OT, Promise */ - isReady = function() { - return !OT.$.isDOMUnloaded() && OT.$.isReady() && - _configReady && _pluginLoadAttemptComplete; - }, +function httpTest(config) { - onLoaded = function() { - if (isReady()) { - OT.dispatchEvent(new OT.EnvLoadedEvent(OT.Event.names.ENV_LOADED)); - } - }, + function otRequest(url, options, callback) { + var request = new XMLHttpRequest(), + _options = options || {}, + _method = _options.method; + + if (!_method) { + callback(new OT.$.Error('No HTTP method specified in options')); + return; + } + + // Setup callbacks to correctly respond to success and error callbacks. This includes + // interpreting the responses HTTP status, which XmlHttpRequest seems to ignore + // by default. + if (callback) { + OTHelpers.on(request, 'load', function(event) { + var status = event.target.status; + + // We need to detect things that XMLHttpRequest considers a success, + // but we consider to be failures. + if (status >= 200 && (status < 300 || status === 304)) { + callback(null, event); + } else { + callback(event); + } + }); + + OTHelpers.on(request, 'error', callback); + } + + request.open(options.method, url, true); + + if (!_options.headers) _options.headers = {}; + + for (var name in _options.headers) { + request.setRequestHeader(name, _options.headers[name]); + } + + return request; + } - onDomReady = function() { - OT.$.onDOMUnload(onDomUnload); + var _httpConfig = config.httpConfig; - // The Dynamic Config won't load until the DOM is ready - OT.Config.load(OT.properties.configURL); + function timeout(delay) { + return new Promise(function(resolve) { + setTimeout(function() { + resolve(); + }, delay); + }); + } - onLoaded(); - }, + function generateRandom10DigitNumber() { + var min = 1000000000; + var max = 9999999999; + return min + Math.floor(Math.random() * (max - min)); + } - onDomUnload = function() { - // Disconnect the session first, this will prevent the plugin - // from locking up during browser unload. - // if (_pluginSupported) { - // var sessions = OT.sessions.where(); - // for (var i=0; i} + */ + function doDownload() { - OT.publishers.destroy(); - OT.subscribers.destroy(); - OT.sessions.destroy('unloaded'); + var xhr; + var startTs; + var loadedLength = 0; + var downloadPromise = new Promise(function(resolve, reject) { + xhr = otRequest([_httpConfig.downloadUrl, '?x=', generateRandom10DigitNumber()].join(''), + {method: 'get'}, function(error) { + if (error) { + reject(new OT.$.Error('Connection to the HTTP server failed (' + + error.target.status + ')', 1006)); + } else { + resolve(); + } + }); - OT.dispatchEvent(new OT.EnvLoadedEvent(OT.Event.names.ENV_UNLOADED)); - }, + xhr.addEventListener('loadstart', function() { + startTs = OT.$.now(); + }); + xhr.addEventListener('progress', function(evt) { + loadedLength = evt.loaded; + }); - onPluginReady = function(err) { - // We mark the plugin as ready so as not to stall the environment - // loader. In this case though, TBPlugin is not supported. - _pluginLoadAttemptComplete = true; + xhr.send(); + }); - if (err) { - OT.debug('TB Plugin failed to load or was not installed'); - } - - onLoaded(); - }, - - configLoaded = function() { - _configReady = true; - OT.Config.off('dynamicConfigChanged', configLoaded); - OT.Config.off('dynamicConfigLoadFailed', configLoadFailed); - - onLoaded(); - }, - - configLoadFailed = function() { - configLoaded(); + return Promise.race([ + downloadPromise, + timeout(_httpConfig.duration * 1000) + ]) + .then(function() { + xhr.abort(); + return { + byteDownloaded: loadedLength, + duration: OT.$.now() - startTs }; + }); + } + function doUpload() { + var payload = new Array(_httpConfig.uploadSize * _httpConfig.uploadCount).join('a'); - OT.Config.on('dynamicConfigChanged', configLoaded); - OT.Config.on('dynamicConfigLoadFailed', configLoadFailed); + var xhr; + var startTs; + var loadedLength = 0; + var uploadPromise = new Promise(function(resolve, reject) { + xhr = otRequest(_httpConfig.uploadUrl, {method: 'post'}, function(error) { + if (error) { + reject(new OT.$.Error('Connection to the HTTP server failed (' + + error.target.status + ')', 1006)); + } else { + resolve(); + } + }); - OT.$.onDOMLoad(onDomReady); + xhr.upload.addEventListener('loadstart', function() { + startTs = OT.$.now(); + }); + xhr.upload.addEventListener('progress', function(evt) { + loadedLength = evt.loaded; + }); - // If the plugin should work on this platform then - // see if it loads. - if (_pluginSupported) TBPlugin.ready(onPluginReady); + xhr.send(payload); + }); - this.onLoad = function(cb, context) { - if (isReady()) { - cb.call(context); - return; + return Promise.race([ + uploadPromise, + timeout(_httpConfig.duration * 1000) + ]) + .then(function() { + xhr.abort(); + return { + byteUploaded: loadedLength, + duration: OT.$.now() - startTs + }; + }); + } + + return Promise.all([doDownload(), doUpload()]) + .then(function(values) { + var downloadStats = values[0]; + var uploadStats = values[1]; + + return { + downloadBandwidth: 1000 * (downloadStats.byteDownloaded * 8) / downloadStats.duration, + uploadBandwidth: 1000 * (uploadStats.byteUploaded * 8) / uploadStats.duration + }; + }); +} + +OT.httpTest = httpTest; + +// tb_require('../../helpers/helpers.js') + +/* exported SDPHelpers */ + +// Here are the structure of the rtpmap attribute and the media line, most of the +// complex Regular Expressions in this code are matching against one of these two +// formats: +// * a=rtpmap: / [/] +// * m= / +// +// References: +// * https://tools.ietf.org/html/rfc4566 +// * http://en.wikipedia.org/wiki/Session_Description_Protocol +// +var SDPHelpers = { + // Search through sdpLines to find the Media Line of type +mediaType+. + getMLineIndex: function getMLineIndex(sdpLines, mediaType) { + var targetMLine = 'm=' + mediaType; + + // Find the index of the media line for +type+ + return OT.$.findIndex(sdpLines, function(line) { + if (line.indexOf(targetMLine) !== -1) { + return true; } - OT.on(OT.Event.names.ENV_LOADED, cb, context); - }; + return false; + }); + }, - this.onUnload = function(cb, context) { - if (this.isUnloaded()) { - cb.call(context); - return; + // Extract the payload types for a give Media Line. + // + getMLinePayloadTypes: function getMLinePayloadTypes (mediaLine, mediaType) { + var mLineSelector = new RegExp('^m=' + mediaType + + ' \\d+(/\\d+)? [a-zA-Z0-9/]+(( [a-zA-Z0-9/]+)+)$', 'i'); + + // Get all payload types that the line supports + var payloadTypes = mediaLine.match(mLineSelector); + if (!payloadTypes || payloadTypes.length < 2) { + // Error, invalid M line? + return []; + } + + return OT.$.trim(payloadTypes[2]).split(' '); + }, + + removeTypesFromMLine: function removeTypesFromMLine (mediaLine, payloadTypes) { + return OT.$.trim( + mediaLine.replace(new RegExp(' ' + payloadTypes.join(' |'), 'ig') , ' ') + .replace(/\s+/g, ' ') ); + }, + + + // Remove all references to a particular encodingName from a particular media type + // + removeMediaEncoding: function removeMediaEncoding (sdp, mediaType, encodingName) { + var sdpLines = sdp.split('\r\n'), + mLineIndex = SDPHelpers.getMLineIndex(sdpLines, mediaType), + mLine = mLineIndex > -1 ? sdpLines[mLineIndex] : void 0, + typesToRemove = [], + payloadTypes, + match; + + if (mLineIndex === -1) { + // Error, missing M line + return sdpLines.join('\r\n'); + } + + // Get all payload types that the line supports + payloadTypes = SDPHelpers.getMLinePayloadTypes(mLine, mediaType); + if (payloadTypes.length === 0) { + // Error, invalid M line? + return sdpLines.join('\r\n'); + } + + // Find the location of all the rtpmap lines that relate to +encodingName+ + // and any of the supported payload types + var matcher = new RegExp('a=rtpmap:(' + payloadTypes.join('|') + ') ' + + encodingName + '\\/\\d+', 'i'); + + sdpLines = OT.$.filter(sdpLines, function(line, index) { + match = line.match(matcher); + if (match === null) return true; + + typesToRemove.push(match[1]); + + if (index < mLineIndex) { + // This removal changed the index of the mline, track it + mLineIndex--; } - OT.on(OT.Event.names.ENV_UNLOADED, cb, context); - }; + // remove this one + return false; + }); - this.isUnloaded = function() { - return OT.$.isDOMUnloaded(); + if (typesToRemove.length > 0 && mLineIndex > -1) { + // Remove all the payload types and we've removed from the media line + sdpLines[mLineIndex] = SDPHelpers.removeTypesFromMLine(mLine, typesToRemove); + } + + return sdpLines.join('\r\n'); + }, + + // Removes all Confort Noise from +sdp+. + // + // See https://jira.tokbox.com/browse/OPENTOK-7176 + // + removeComfortNoise: function removeComfortNoise (sdp) { + return SDPHelpers.removeMediaEncoding(sdp, 'audio', 'CN'); + }, + + removeVideoCodec: function removeVideoCodec (sdp, codec) { + return SDPHelpers.removeMediaEncoding(sdp, 'video', codec); + } +}; + + + +// tb_require('../../helpers/helpers.js') + +function isVideoStat(stat) { + // Chrome implementation only has this property for RTP video stat + return stat.hasOwnProperty('googFrameWidthReceived') || + stat.hasOwnProperty('googFrameWidthInput') || + stat.mediaType === 'video'; +} + +function isAudioStat(stat) { + // Chrome implementation only has this property for RTP audio stat + return stat.hasOwnProperty('audioInputLevel') || + stat.hasOwnProperty('audioOutputLevel') || + stat.mediaType === 'audio'; +} + +function isInboundStat(stat) { + return stat.hasOwnProperty('bytesReceived'); +} + +function parseStatCategory(stat) { + var statCategory = { + packetsLost: 0, + packetsReceived: 0, + bytesReceived: 0 + }; + + if (stat.hasOwnProperty('packetsReceived')) { + statCategory.packetsReceived = parseInt(stat.packetsReceived, 10); + } + if (stat.hasOwnProperty('packetsLost')) { + statCategory.packetsLost = parseInt(stat.packetsLost, 10); + } + if (stat.hasOwnProperty('bytesReceived')) { + statCategory.bytesReceived += parseInt(stat.bytesReceived, 10); + } + + return statCategory; +} + +function normalizeTimestamp(timestamp) { + if (OT.$.isObject(timestamp) && 'getTime' in timestamp) { + // Chrome as of 39 delivers a "kind of Date" object for timestamps + // we duck check it and get the timestamp + return timestamp.getTime(); + } else { + return timestamp; + } +} + +var getStatsHelpers = {}; +getStatsHelpers.isVideoStat = isVideoStat; +getStatsHelpers.isAudioStat = isAudioStat; +getStatsHelpers.isInboundStat = isInboundStat; +getStatsHelpers.parseStatCategory = parseStatCategory; +getStatsHelpers.normalizeTimestamp = normalizeTimestamp; + +OT.getStatsHelpers = getStatsHelpers; + +// tb_require('../../helpers/helpers.js') + +/** + * + * @returns {function(RTCPeerConnection, + * function(DOMError, Array.<{id: string=, type: string=, timestamp: number}>))} + */ +function getStatsAdapter() { + + /// +// Get Stats using the older API. Used by all current versions +// of Chrome. +// + function getStatsOldAPI(peerConnection, completion) { + + peerConnection.getStats(function(rtcStatsReport) { + + var stats = []; + rtcStatsReport.result().forEach(function(rtcStat) { + + var stat = {}; + + rtcStat.names().forEach(function(name) { + stat[name] = rtcStat.stat(name); + }); + + // fake the structure of the "new" RTC stat object + stat.id = rtcStat.id; + stat.type = rtcStat.type; + stat.timestamp = rtcStat.timestamp; + stats.push(stat); + }); + + completion(null, stats); + }); + } + +/// +// Get Stats using the newer API. +// + function getStatsNewAPI(peerConnection, completion) { + + peerConnection.getStats(null, function(rtcStatsReport) { + + var stats = []; + rtcStatsReport.forEach(function(rtcStats) { + stats.push(rtcStats); + }); + + completion(null, stats); + }, completion); + } + + if (OT.$.browserVersion().name === 'Firefox' || OTPlugin.isInstalled()) { + return getStatsNewAPI; + } else { + return getStatsOldAPI; + } +} + +OT.getStatsAdpater = getStatsAdapter; + +// tb_require('../../helpers/helpers.js') +// tb_require('../peer_connection/get_stats_adapter.js') +// tb_require('../peer_connection/get_stats_helpers.js') + +/* global OT, Promise */ + +/** + * @returns {Promise.<{packetLostRation: number, roundTripTime: number}>} + */ +function webrtcTest(config) { + + var _getStats = OT.getStatsAdpater(); + var _mediaConfig = config.mediaConfig; + var _localStream = config.localStream; + + // todo copied from peer_connection.js + // Normalise these + var NativeRTCSessionDescription, + NativeRTCIceCandidate; + if (!OTPlugin.isInstalled()) { + // order is very important: 'RTCSessionDescription' defined in Firefox Nighly but useless + NativeRTCSessionDescription = (window.mozRTCSessionDescription || + window.RTCSessionDescription); + NativeRTCIceCandidate = (window.mozRTCIceCandidate || window.RTCIceCandidate); + } + else { + NativeRTCSessionDescription = OTPlugin.RTCSessionDescription; + NativeRTCIceCandidate = OTPlugin.RTCIceCandidate; + } + + + function isCandidateRelay(candidate) { + return candidate.candidate.indexOf('relay') !== -1; + } + + /** + * Create video a element attaches it to the body and put it visually outside the body. + * + * @returns {OT.VideoElement} + */ + function createVideoElementForTest() { + var videoElement = new OT.VideoElement({attributes: {muted: true}}); + videoElement.domElement().style.position = 'absolute'; + videoElement.domElement().style.top = '-9999%'; + videoElement.appendTo(document.body); + return videoElement; + } + + function createPeerConnectionForTest() { + return new Promise(function(resolve, reject) { + OT.$.createPeerConnection({ + iceServers: _mediaConfig.iceServers + }, {}, + null, + function(error, pc) { + if (error) { + reject(new OT.$.Error('createPeerConnection failed', 1600, error)); + } else { + resolve(pc); + } + } + ); + }); + } + + function createOffer(pc) { + return new Promise(function(resolve, reject) { + pc.createOffer(resolve, reject); + }); + } + + function attachMediaStream(videoElement, webRtcStream) { + return new Promise(function(resolve, reject) { + videoElement.bindToStream(webRtcStream, function(error) { + if (error) { + reject(new OT.$.Error('bindToStream failed', 1600, error)); + } else { + resolve(); + } + }); + }); + } + + function addIceCandidate(pc, candidate) { + return new Promise(function(resolve, reject) { + pc.addIceCandidate(new NativeRTCIceCandidate({ + sdpMLineIndex: candidate.sdpMLineIndex, + candidate: candidate.candidate + }), resolve, reject); + }); + } + + function setLocalDescription(pc, offer) { + return new Promise(function(resolve, reject) { + pc.setLocalDescription(offer, resolve, function(error) { + reject(new OT.$.Error('setLocalDescription failed', 1600, error)); + }); + }); + } + + function setRemoteDescription(pc, offer) { + return new Promise(function(resolve, reject) { + pc.setRemoteDescription(offer, resolve, function(error) { + reject(new OT.$.Error('setRemoteDescription failed', 1600, error)); + }); + }); + } + + function createAnswer(pc) { + return new Promise(function(resolve, reject) { + pc.createAnswer(resolve, function(error) { + reject(new OT.$.Error('createAnswer failed', 1600, error)); + }); + }); + } + + function getStats(pc) { + return new Promise(function(resolve, reject) { + _getStats(pc, function(error, stats) { + if (error) { + reject(new OT.$.Error('geStats failed', 1600, error)); + } else { + resolve(stats); + } + }); + }); + } + + function createOnIceCandidateListener(pc) { + return function(event) { + if (event.candidate && isCandidateRelay(event.candidate)) { + addIceCandidate(pc, event.candidate)['catch'](function() { + OT.warn('An error occurred while adding a ICE candidate during webrtc test'); + }); + } }; } - var EnvLoader = new EnvironmentLoader(); + /** + * @returns {Promise.<{packetLostRation: number, roundTripTime: number}>} + */ + function collectPeerConnectionStats(localPc, remotePc) { - OT.onLoad = function(cb, context) { - EnvLoader.onLoad(cb, context); + var SAMPLING_DELAY = 1000; + + return new Promise(function(resolve) { + + var collectionActive = true; + + var statsSamples = { + startTs: OT.$.now(), + packetLostRatioSamplesCount: 0, + packetLostRatio: 0, + roundTripTimeSamplesCount: 0, + roundTripTime: 0, + bytesReceived: 0 + }; + + function calculateBandwidth() { + return 1000 * statsSamples.bytesReceived * 8 / (OT.$.now() - statsSamples.startTs); + } + + function sample() { + + Promise.all([ + getStats(localPc).then(function(stats) { + OT.$.forEach(stats, function(stat) { + if (OT.getStatsHelpers.isVideoStat(stat)) { + var rtt = null; + + if (stat.hasOwnProperty('googRtt')) { + rtt = parseInt(stat.googRtt, 10); + } else if (stat.hasOwnProperty('mozRtt')) { + rtt = stat.mozRtt; + } + + if (rtt !== null && rtt > -1) { + statsSamples.roundTripTimeSamplesCount++; + statsSamples.roundTripTime += rtt; + } + } + }); + }), + + getStats(remotePc).then(function(stats) { + OT.$.forEach(stats, function(stat) { + if (OT.getStatsHelpers.isVideoStat(stat)) { + if (stat.hasOwnProperty('packetsReceived') && + stat.hasOwnProperty('packetsLost')) { + + var packetLost = parseInt(stat.packetsLost, 10); + var packetsReceived = parseInt(stat.packetsReceived, 10); + if (packetLost >= 0 && packetsReceived > 0) { + statsSamples.packetLostRatioSamplesCount++; + statsSamples.packetLostRatio += packetLost * 100 / packetsReceived; + } + } + + if (stat.hasOwnProperty('bytesReceived')) { + statsSamples.bytesReceived += parseInt(stat.bytesReceived, 10); + } + } + }); + }) + ]) + .then(function() { + // wait and trigger another round of collection + setTimeout(function() { + if (collectionActive) { + sample(); + } + }, SAMPLING_DELAY); + }); + } + + // start the sampling "loop" + sample(); + + function stopCollectStats() { + collectionActive = false; + + var pcStats = { + packetLostRatio: statsSamples.packetLostRatioSamplesCount > 0 ? + statsSamples.packetLostRatio /= statsSamples.packetLostRatioSamplesCount * 100 : null, + roundTripTime: statsSamples.roundTripTimeSamplesCount > 0 ? + statsSamples.roundTripTime /= statsSamples.roundTripTimeSamplesCount : null, + bandwidth: calculateBandwidth() + }; + + resolve(pcStats); + } + + // sample for the nominal delay + // if the bandwidth is bellow the threshold at the end we give an extra time + setTimeout(function() { + + if (calculateBandwidth() < _mediaConfig.thresholdBitsPerSecond) { + // give an extra delay in case it was transient bandwidth problem + setTimeout(stopCollectStats, _mediaConfig.extendedDuration * 1000); + } else { + stopCollectStats(); + } + + }, _mediaConfig.duration * 1000); + }); + } + + return Promise + .all([createPeerConnectionForTest(), createPeerConnectionForTest()]) + .then(function(pcs) { + + var localPc = pcs[0], + remotePc = pcs[1]; + + var localVideo = createVideoElementForTest(), + remoteVideo = createVideoElementForTest(); + + attachMediaStream(localVideo, _localStream); + localPc.addStream(_localStream); + + var remoteStream; + remotePc.onaddstream = function(evt) { + remoteStream = evt.stream; + attachMediaStream(remoteVideo, remoteStream); + }; + + localPc.onicecandidate = createOnIceCandidateListener(remotePc); + remotePc.onicecandidate = createOnIceCandidateListener(localPc); + + function dispose() { + localVideo.destroy(); + remoteVideo.destroy(); + localPc.close(); + remotePc.close(); + } + + return createOffer(localPc) + .then(function(offer) { + return Promise.all([ + setLocalDescription(localPc, offer), + setRemoteDescription(remotePc, offer) + ]); + }) + .then(function() { + return createAnswer(remotePc); + }) + .then(function(answer) { + return Promise.all([ + setLocalDescription(remotePc, answer), + setRemoteDescription(localPc, answer) + ]); + }) + .then(function() { + return collectPeerConnectionStats(localPc, remotePc); + }) + .then(function(value) { + dispose(); + return value; + }, function(error) { + dispose(); + throw error; + }); + }); +} + +OT.webrtcTest = webrtcTest; + +// tb_require('../../helpers/helpers.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ + +// Manages N Chrome elements +OT.Chrome = function(properties) { + var _visible = false, + _widgets = {}, + + // Private helper function + _set = function(name, widget) { + widget.parent = this; + widget.appendTo(properties.parent); + + _widgets[name] = widget; + + this[name] = widget; + }; + + if (!properties.parent) { + // @todo raise an exception + return; + } + + OT.$.eventing(this); + + this.destroy = function() { + this.off(); + this.hideWhileLoading(); + + for (var name in _widgets) { + _widgets[name].destroy(); + } }; - OT.onUnload = function(cb, context) { - EnvLoader.onUnload(cb, context); + this.showAfterLoading = function() { + _visible = true; + + for (var name in _widgets) { + _widgets[name].showAfterLoading(); + } }; - OT.isUnloaded = function() { - return EnvLoader.isUnloaded(); + this.hideWhileLoading = function() { + _visible = false; + + for (var name in _widgets) { + _widgets[name].hideWhileLoading(); + } + }; + + + // Adds the widget to the chrome and to the DOM. Also creates a accessor + // property for it on the chrome. + // + // @example + // chrome.set('foo', new FooWidget()); + // chrome.foo.setDisplayMode('on'); + // + // @example + // chrome.set({ + // foo: new FooWidget(), + // bar: new BarWidget() + // }); + // chrome.foo.setDisplayMode('on'); + // + this.set = function(widgetName, widget) { + if (typeof(widgetName) === 'string' && widget) { + _set.call(this, widgetName, widget); + + } else { + for (var name in widgetName) { + if (widgetName.hasOwnProperty(name)) { + _set.call(this, name, widgetName[name]); + } + } + } + return this; + }; + +}; + + +// tb_require('../../../helpers/helpers.js') +// tb_require('../chrome.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ + +if (!OT.Chrome.Behaviour) OT.Chrome.Behaviour = {}; + +// A mixin to encapsulate the basic widget behaviour. This needs a better name, +// it's not actually a widget. It's actually "Behaviour that can be applied to +// an object to make it support the basic Chrome widget workflow"...but that would +// probably been too long a name. +OT.Chrome.Behaviour.Widget = function(widget, options) { + var _options = options || {}, + _mode, + _previousOnMode = 'auto', + _loadingMode; + + // + // @param [String] mode + // 'on', 'off', or 'auto' + // + widget.setDisplayMode = function(mode) { + var newMode = mode || 'auto'; + if (_mode === newMode) return; + + OT.$.removeClass(this.domElement, 'OT_mode-' + _mode); + OT.$.addClass(this.domElement, 'OT_mode-' + newMode); + + if (newMode === 'off') { + _previousOnMode = _mode; + } + _mode = newMode; + }; + + widget.getDisplayMode = function() { + return _mode; + }; + + widget.show = function() { + if (_mode !== _previousOnMode) { + this.setDisplayMode(_previousOnMode); + if (_options.onShow) _options.onShow(); + } + return this; + }; + + widget.showAfterLoading = function() { + this.setDisplayMode(_loadingMode); + }; + + widget.hide = function() { + if (_mode !== 'off') { + this.setDisplayMode('off'); + if (_options.onHide) _options.onHide(); + } + return this; + }; + + widget.hideWhileLoading = function() { + _loadingMode = _mode; + this.setDisplayMode('off'); + }; + + widget.destroy = function() { + if (_options.onDestroy) _options.onDestroy(this.domElement); + if (this.domElement) OT.$.removeElement(this.domElement); + + return widget; + }; + + widget.appendTo = function(parent) { + // create the element under parent + this.domElement = OT.$.createElement(_options.nodeName || 'div', + _options.htmlAttributes, + _options.htmlContent); + + if (_options.onCreate) _options.onCreate(this.domElement); + + widget.setDisplayMode(_options.mode); + + if (_options.mode === 'auto') { + // if the mode is auto we hold the "on mode" for 2 seconds + // this will let the proper widgets nicely fade away and help discoverability + OT.$.addClass(widget.domElement, 'OT_mode-on-hold'); + setTimeout(function() { + OT.$.removeClass(widget.domElement, 'OT_mode-on-hold'); + }, 2000); + } + + + // add the widget to the parent + parent.appendChild(this.domElement); + + return widget; + }; +}; + +// tb_require('../../helpers/helpers.js') +// tb_require('./behaviour/widget.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ + +OT.Chrome.VideoDisabledIndicator = function(options) { + var _videoDisabled = false, + _warning = false, + updateClasses; + + updateClasses = OT.$.bind(function(domElement) { + if (_videoDisabled) { + OT.$.addClass(domElement, 'OT_video-disabled'); + } else { + OT.$.removeClass(domElement, 'OT_video-disabled'); + } + if(_warning) { + OT.$.addClass(domElement, 'OT_video-disabled-warning'); + } else { + OT.$.removeClass(domElement, 'OT_video-disabled-warning'); + } + if ((_videoDisabled || _warning) && + (this.getDisplayMode() === 'auto' || this.getDisplayMode() === 'on')) { + OT.$.addClass(domElement, 'OT_active'); + } else { + OT.$.removeClass(domElement, 'OT_active'); + } + }, this); + + this.disableVideo = function(value) { + _videoDisabled = value; + if(value === true) { + _warning = false; + } + updateClasses(this.domElement); + }; + + this.setWarning = function(value) { + _warning = value; + updateClasses(this.domElement); + }; + + // Mixin common widget behaviour + OT.Chrome.Behaviour.Widget(this, { + mode: options.mode || 'auto', + nodeName: 'div', + htmlAttributes: { + className: 'OT_video-disabled-indicator' + } + }); + + var parentSetDisplayMode = OT.$.bind(this.setDisplayMode, this); + this.setDisplayMode = function(mode) { + parentSetDisplayMode(mode); + updateClasses(this.domElement); + }; +}; + +// tb_require('../../helpers/helpers.js') +// tb_require('./behaviour/widget.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ + +// NamePanel Chrome Widget +// +// mode (String) +// Whether to display the name. Possible values are: "auto" (the name is displayed +// when the stream is first displayed and when the user mouses over the display), +// "off" (the name is not displayed), and "on" (the name is displayed). +// +// displays a name +// can be shown/hidden +// can be destroyed +OT.Chrome.NamePanel = function(options) { + var _name = options.name; + + if (!_name || OT.$.trim(_name).length === '') { + _name = null; + + // THere's no name, just flip the mode off + options.mode = 'off'; + } + + this.setName = OT.$.bind(function(name) { + if (!_name) this.setDisplayMode('auto'); + _name = name; + this.domElement.innerHTML = _name; + }); + + // Mixin common widget behaviour + OT.Chrome.Behaviour.Widget(this, { + mode: options.mode, + nodeName: 'h1', + htmlContent: _name, + htmlAttributes: { + className: 'OT_name OT_edge-bar-item' + } + }); +}; + +// tb_require('../../helpers/helpers.js') +// tb_require('./behaviour/widget.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ + +OT.Chrome.MuteButton = function(options) { + var _onClickCb, + _muted = options.muted || false, + updateClasses, + attachEvents, + detachEvents, + onClick; + + updateClasses = OT.$.bind(function() { + if (_muted) { + OT.$.addClass(this.domElement, 'OT_active'); + } else { + OT.$.removeClass(this.domElement, 'OT_active '); + } + }, this); + + // Private Event Callbacks + attachEvents = function(elem) { + _onClickCb = OT.$.bind(onClick, this); + OT.$.on(elem, 'click', _onClickCb); + }; + + detachEvents = function(elem) { + _onClickCb = null; + OT.$.off(elem, 'click', _onClickCb); + }; + + onClick = function() { + _muted = !_muted; + + updateClasses(); + + if (_muted) { + this.parent.trigger('muted', this); + } else { + this.parent.trigger('unmuted', this); + } + + return false; + }; + + OT.$.defineProperties(this, { + muted: { + get: function() { return _muted; }, + set: function(muted) { + _muted = muted; + updateClasses(); + } + } + }); + + // Mixin common widget behaviour + var classNames = _muted ? 'OT_edge-bar-item OT_mute OT_active' : 'OT_edge-bar-item OT_mute'; + OT.Chrome.Behaviour.Widget(this, { + mode: options.mode, + nodeName: 'button', + htmlContent: 'Mute', + htmlAttributes: { + className: classNames + }, + onCreate: OT.$.bind(attachEvents, this), + onDestroy: OT.$.bind(detachEvents, this) + }); +}; + +// tb_require('../../helpers/helpers.js') +// tb_require('./behaviour/widget.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ + +// BackingBar Chrome Widget +// +// nameMode (String) +// Whether or not the name panel is being displayed +// Possible values are: "auto" (the name is displayed +// when the stream is first displayed and when the user mouses over the display), +// "off" (the name is not displayed), and "on" (the name is displayed). +// +// muteMode (String) +// Whether or not the mute button is being displayed +// Possible values are: "auto" (the mute button is displayed +// when the stream is first displayed and when the user mouses over the display), +// "off" (the mute button is not displayed), and "on" (the mute button is displayed). +// +// displays a backing bar +// can be shown/hidden +// can be destroyed +OT.Chrome.BackingBar = function(options) { + var _nameMode = options.nameMode, + _muteMode = options.muteMode; + + function getDisplayMode() { + if(_nameMode === 'on' || _muteMode === 'on') { + return 'on'; + } else if(_nameMode === 'mini' || _muteMode === 'mini') { + return 'mini'; + } else if(_nameMode === 'mini-auto' || _muteMode === 'mini-auto') { + return 'mini-auto'; + } else if(_nameMode === 'auto' || _muteMode === 'auto') { + return 'auto'; + } else { + return 'off'; + } + } + + // Mixin common widget behaviour + OT.Chrome.Behaviour.Widget(this, { + mode: getDisplayMode(), + nodeName: 'div', + htmlContent: '', + htmlAttributes: { + className: 'OT_bar OT_edge-bar-item' + } + }); + + this.setNameMode = function(nameMode) { + _nameMode = nameMode; + this.setDisplayMode(getDisplayMode()); + }; + + this.setMuteMode = function(muteMode) { + _muteMode = muteMode; + this.setDisplayMode(getDisplayMode()); + }; + +}; + + +// tb_require('../../helpers/helpers.js') +// tb_require('./behaviour/widget.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ + + +OT.Chrome.AudioLevelMeter = function(options) { + + var widget = this, + _meterBarElement, + _voiceOnlyIconElement, + _meterValueElement, + _value, + _maxValue = options.maxValue || 1, + _minValue = options.minValue || 0; + + function onCreate() { + _meterBarElement = OT.$.createElement('div', { + className: 'OT_audio-level-meter__bar' + }, ''); + _meterValueElement = OT.$.createElement('div', { + className: 'OT_audio-level-meter__value' + }, ''); + _voiceOnlyIconElement = OT.$.createElement('div', { + className: 'OT_audio-level-meter__audio-only-img' + }, ''); + + widget.domElement.appendChild(_meterBarElement); + widget.domElement.appendChild(_voiceOnlyIconElement); + widget.domElement.appendChild(_meterValueElement); + } + + function updateView() { + var percentSize = _value * 100 / (_maxValue - _minValue); + _meterValueElement.style.width = _meterValueElement.style.height = 2 * percentSize + '%'; + _meterValueElement.style.top = _meterValueElement.style.right = -percentSize + '%'; + } + + // Mixin common widget behaviour + var widgetOptions = { + mode: options ? options.mode : 'auto', + nodeName: 'div', + htmlAttributes: { + className: 'OT_audio-level-meter' + }, + onCreate: onCreate + }; + + OT.Chrome.Behaviour.Widget(this, widgetOptions); + + // override + var _setDisplayMode = OT.$.bind(widget.setDisplayMode, widget); + widget.setDisplayMode = function(mode) { + _setDisplayMode(mode); + if (mode === 'off') { + if (options.onPassivate) options.onPassivate(); + } else { + if (options.onActivate) options.onActivate(); + } + }; + + widget.setValue = function(value) { + _value = value; + updateView(); + }; +}; + +// tb_require('../../helpers/helpers.js') +// tb_require('./behaviour/widget.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ + + +// Archving Chrome Widget +// +// mode (String) +// Whether to display the archving widget. Possible values are: "on" (the status is displayed +// when archiving and briefly when archving ends) and "off" (the status is not displayed) + +// Whether to display the archving widget. Possible values are: "auto" (the name is displayed +// when the status is first displayed and when the user mouses over the display), +// "off" (the name is not displayed), and "on" (the name is displayed). +// +// displays a name +// can be shown/hidden +// can be destroyed +OT.Chrome.Archiving = function(options) { + var _archiving = options.archiving, + _archivingStarted = options.archivingStarted || 'Archiving on', + _archivingEnded = options.archivingEnded || 'Archiving off', + _initialState = true, + _lightBox, + _light, + _text, + _textNode, + renderStageDelayedAction, + renderText, + renderStage; + + renderText = function(text) { + _textNode.nodeValue = text; + _lightBox.setAttribute('title', text); + }; + + renderStage = OT.$.bind(function() { + if(renderStageDelayedAction) { + clearTimeout(renderStageDelayedAction); + renderStageDelayedAction = null; + } + + if(_archiving) { + OT.$.addClass(_light, 'OT_active'); + } else { + OT.$.removeClass(_light, 'OT_active'); + } + + OT.$.removeClass(this.domElement, 'OT_archiving-' + (!_archiving ? 'on' : 'off')); + OT.$.addClass(this.domElement, 'OT_archiving-' + (_archiving ? 'on' : 'off')); + if(options.show && _archiving) { + renderText(_archivingStarted); + OT.$.addClass(_text, 'OT_mode-on'); + OT.$.removeClass(_text, 'OT_mode-auto'); + this.setDisplayMode('on'); + renderStageDelayedAction = setTimeout(function() { + OT.$.addClass(_text, 'OT_mode-auto'); + OT.$.removeClass(_text, 'OT_mode-on'); + }, 5000); + } else if(options.show && !_initialState) { + OT.$.addClass(_text, 'OT_mode-on'); + OT.$.removeClass(_text, 'OT_mode-auto'); + this.setDisplayMode('on'); + renderText(_archivingEnded); + renderStageDelayedAction = setTimeout(OT.$.bind(function() { + this.setDisplayMode('off'); + }, this), 5000); + } else { + this.setDisplayMode('off'); + } + }, this); + + // Mixin common widget behaviour + OT.Chrome.Behaviour.Widget(this, { + mode: _archiving && options.show && 'on' || 'off', + nodeName: 'h1', + htmlAttributes: {className: 'OT_archiving OT_edge-bar-item OT_edge-bottom'}, + onCreate: OT.$.bind(function() { + _lightBox = OT.$.createElement('div', { + className: 'OT_archiving-light-box' + }, ''); + _light = OT.$.createElement('div', { + className: 'OT_archiving-light' + }, ''); + _lightBox.appendChild(_light); + _text = OT.$.createElement('div', { + className: 'OT_archiving-status OT_mode-on OT_edge-bar-item OT_edge-bottom' + }, ''); + _textNode = document.createTextNode(''); + _text.appendChild(_textNode); + this.domElement.appendChild(_lightBox); + this.domElement.appendChild(_text); + renderStage(); + }, this) + }); + + this.setShowArchiveStatus = OT.$.bind(function(show) { + options.show = show; + if(this.domElement) { + renderStage.call(this); + } + }, this); + + this.setArchiving = OT.$.bind(function(status) { + _archiving = status; + _initialState = false; + if(this.domElement) { + renderStage.call(this); + } + }, this); + +}; + +// tb_require('../helpers.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ + +// Web OT Helpers +!(function(window) { + // guard for Node.js + if (window && typeof(navigator) !== 'undefined') { + var NativeRTCPeerConnection = (window.webkitRTCPeerConnection || + window.mozRTCPeerConnection); + + if (navigator.webkitGetUserMedia) { + /*global webkitMediaStream, webkitRTCPeerConnection*/ + // Stub for getVideoTracks for Chrome < 26 + if (!webkitMediaStream.prototype.getVideoTracks) { + webkitMediaStream.prototype.getVideoTracks = function() { + return this.videoTracks; + }; + } + + // Stubs for getAudioTracks for Chrome < 26 + if (!webkitMediaStream.prototype.getAudioTracks) { + webkitMediaStream.prototype.getAudioTracks = function() { + return this.audioTracks; + }; + } + + if (!webkitRTCPeerConnection.prototype.getLocalStreams) { + webkitRTCPeerConnection.prototype.getLocalStreams = function() { + return this.localStreams; + }; + } + + if (!webkitRTCPeerConnection.prototype.getRemoteStreams) { + webkitRTCPeerConnection.prototype.getRemoteStreams = function() { + return this.remoteStreams; + }; + } + + } else if (navigator.mozGetUserMedia) { + // Firefox < 23 doesn't support get Video/Audio tracks, we'll just stub them out for now. + /* global MediaStream */ + if (!MediaStream.prototype.getVideoTracks) { + MediaStream.prototype.getVideoTracks = function() { + return []; + }; + } + + if (!MediaStream.prototype.getAudioTracks) { + MediaStream.prototype.getAudioTracks = function() { + return []; + }; + } + + // This won't work as mozRTCPeerConnection is a weird internal Firefox + // object (a wrapped native object I think). + // if (!window.mozRTCPeerConnection.prototype.getLocalStreams) { + // window.mozRTCPeerConnection.prototype.getLocalStreams = function() { + // return this.localStreams; + // }; + // } + + // This won't work as mozRTCPeerConnection is a weird internal Firefox + // object (a wrapped native object I think). + // if (!window.mozRTCPeerConnection.prototype.getRemoteStreams) { + // window.mozRTCPeerConnection.prototype.getRemoteStreams = function() { + // return this.remoteStreams; + // }; + // } + } + + // The setEnabled method on MediaStreamTracks is a OTPlugin + // construct. In this particular instance it's easier to bring + // all the good browsers down to IE's level than bootstrap it up. + if (typeof window.MediaStreamTrack !== 'undefined') { + if (!window.MediaStreamTrack.prototype.setEnabled) { + window.MediaStreamTrack.prototype.setEnabled = function (enabled) { + this.enabled = OT.$.castToBoolean(enabled); + }; + } + } + + if (!window.URL && window.webkitURL) { + window.URL = window.webkitURL; + } + + OT.$.createPeerConnection = function (config, options, publishersWebRtcStream, completion) { + if (OTPlugin.isInstalled()) { + OTPlugin.initPeerConnection(config, options, + publishersWebRtcStream, completion); + } + else { + var pc; + + try { + pc = new NativeRTCPeerConnection(config, options); + } catch(e) { + completion(e.message); + return; + } + + completion(null, pc); + } + }; + } + + // Returns a String representing the supported WebRTC crypto scheme. The possible + // values are SDES_SRTP, DTLS_SRTP, and NONE; + // + // Broadly: + // * Firefox only supports DTLS + // * Older versions of Chrome (<= 24) only support SDES + // * Newer versions of Chrome (>= 25) support DTLS and SDES + // + OT.$.supportedCryptoScheme = function() { + return OT.$.env.name === 'Chrome' && OT.$.env.version < 25 ? 'SDES_SRTP' : 'DTLS_SRTP'; + }; + +})(window); + +// tb_require('../../helpers/helpers.js') +// tb_require('../../helpers/lib/web_rtc.js') + +/* exported subscribeProcessor */ +/* global SDPHelpers */ + +// Attempt to completely process a subscribe message. This will: +// * create an Offer +// * set the new offer as the location description +// +// If there are no issues, the +success+ callback will be executed on completion. +// Errors during any step will result in the +failure+ callback being executed. +// +var subscribeProcessor = function(peerConnection, success, failure) { + var constraints, + generateErrorCallback, + setLocalDescription; + + constraints = { + mandatory: {}, + optional: [] + }, + + generateErrorCallback = function(message, prefix) { + return function(errorReason) { + OT.error(message); + OT.error(errorReason); + + if (failure) failure(message, errorReason, prefix); + }; + }; + + setLocalDescription = function(offer) { + offer.sdp = SDPHelpers.removeComfortNoise(offer.sdp); + offer.sdp = SDPHelpers.removeVideoCodec(offer.sdp, 'ulpfec'); + offer.sdp = SDPHelpers.removeVideoCodec(offer.sdp, 'red'); + + peerConnection.setLocalDescription( + offer, + + // Success + function() { + success(offer); + }, + + // Failure + generateErrorCallback('Error while setting LocalDescription', 'SetLocalDescription') + ); + }; + + // For interop with FireFox. Disable Data Channel in createOffer. + if (navigator.mozGetUserMedia) { + constraints.mandatory.MozDontOfferDataChannel = true; + } + + peerConnection.createOffer( + // Success + setLocalDescription, + + // Failure + generateErrorCallback('Error while creating Offer', 'CreateOffer'), + + constraints + ); +}; +// tb_require('../../helpers/helpers.js') +// tb_require('../../helpers/lib/web_rtc.js') + +/* exported offerProcessor */ +/* global SDPHelpers */ + +// Attempt to completely process +offer+. This will: +// * set the offer as the remote description +// * create an answer and +// * set the new answer as the location description +// +// If there are no issues, the +success+ callback will be executed on completion. +// Errors during any step will result in the +failure+ callback being executed. +// +var offerProcessor = function(peerConnection, offer, success, failure) { + var generateErrorCallback, + setLocalDescription, + createAnswer; + + generateErrorCallback = function(message, prefix) { + return function(errorReason) { + OT.error(message); + OT.error(errorReason); + + if (failure) failure(message, errorReason, prefix); + }; + }; + + setLocalDescription = function(answer) { + answer.sdp = SDPHelpers.removeComfortNoise(answer.sdp); + answer.sdp = SDPHelpers.removeVideoCodec(answer.sdp, 'ulpfec'); + answer.sdp = SDPHelpers.removeVideoCodec(answer.sdp, 'red'); + + peerConnection.setLocalDescription( + answer, + + // Success + function() { + success(answer); + }, + + // Failure + generateErrorCallback('Error while setting LocalDescription', 'SetLocalDescription') + ); + }; + + createAnswer = function() { + peerConnection.createAnswer( + // Success + setLocalDescription, + + // Failure + generateErrorCallback('Error while setting createAnswer', 'CreateAnswer'), + + null, // MediaConstraints + false // createProvisionalAnswer + ); + }; + + // Workaround for a Chrome issue. Add in the SDES crypto line into offers + // from Firefox + if (offer.sdp.indexOf('a=crypto') === -1) { + var cryptoLine = 'a=crypto:1 AES_CM_128_HMAC_SHA1_80 ' + + 'inline:FakeFakeFakeFakeFakeFakeFakeFakeFakeFake\\r\\n'; + + // insert the fake crypto line for every M line + offer.sdp = offer.sdp.replace(/^c=IN(.*)$/gmi, 'c=IN$1\r\n'+cryptoLine); + } + + if (offer.sdp.indexOf('a=rtcp-fb') === -1) { + var rtcpFbLine = 'a=rtcp-fb:* ccm fir\r\na=rtcp-fb:* nack '; + + // insert the fake crypto line for every M line + offer.sdp = offer.sdp.replace(/^m=video(.*)$/gmi, 'm=video$1\r\n'+rtcpFbLine); + } + + peerConnection.setRemoteDescription( + offer, + + // Success + createAnswer, + + // Failure + generateErrorCallback('Error while setting RemoteDescription', 'SetRemoteDescription') + ); + +}; +// tb_require('../../helpers/helpers.js') +// tb_require('../../helpers/lib/web_rtc.js') + +/* exported IceCandidateProcessor */ + +// Normalise these +var NativeRTCIceCandidate; + +if (!OTPlugin.isInstalled()) { + NativeRTCIceCandidate = (window.mozRTCIceCandidate || window.RTCIceCandidate); +} +else { + NativeRTCIceCandidate = OTPlugin.RTCIceCandidate; +} + +// Process incoming Ice Candidates from a remote connection (which have been +// forwarded via iceCandidateForwarder). The Ice Candidates cannot be processed +// until a PeerConnection is available. Once a PeerConnection becomes available +// the pending PeerConnections can be processed by calling processPending. +// +// @example +// +// var iceProcessor = new IceCandidateProcessor(); +// iceProcessor.process(iceMessage1); +// iceProcessor.process(iceMessage2); +// iceProcessor.process(iceMessage3); +// +// iceProcessor.setPeerConnection(peerConnection); +// iceProcessor.processPending(); +// +var IceCandidateProcessor = function() { + var _pendingIceCandidates = [], + _peerConnection = null; + + this.setPeerConnection = function(peerConnection) { + _peerConnection = peerConnection; + }; + + this.process = function(message) { + var iceCandidate = new NativeRTCIceCandidate(message.content); + + if (_peerConnection) { + _peerConnection.addIceCandidate(iceCandidate); + } else { + _pendingIceCandidates.push(iceCandidate); + } + }; + + this.processPending = function() { + while(_pendingIceCandidates.length) { + _peerConnection.addIceCandidate(_pendingIceCandidates.shift()); + } + }; +}; +// tb_require('../../helpers/helpers.js') +// tb_require('../../helpers/lib/web_rtc.js') + +/* exported connectionStateLogger */ + +// @meta: ping Mike/Eric to let them know that this data is coming and what format it will be +// @meta: what reports would I like around this, what question am I trying to answer? +// +// log the sequence of iceconnectionstates, icegatheringstates, signalingstates +// log until we reach a terminal iceconnectionstate or signalingstate +// send a client event once we have a full sequence +// +// Format of the states: +// [ +// {delta: 1234, iceConnection: 'new', signalingstate: 'stable', iceGathering: 'new'}, +// {delta: 1234, iceConnection: 'new', signalingstate: 'stable', iceGathering: 'new'}, +// {delta: 1234, iceConnection: 'new', signalingstate: 'stable', iceGathering: 'new'}, +// ] +// +// Format of the logged event: +// { +// startTime: 1234, +// finishTime: 5678, +// states: [ +// {delta: 1234, iceConnection: 'new', signalingstate: 'stable', iceGathering: 'new'}, +// {delta: 1234, iceConnection: 'new', signalingstate: 'stable', iceGathering: 'new'}, +// {delta: 1234, iceConnection: 'new', signalingstate: 'stable', iceGathering: 'new'}, +// ] +// } +// +var connectionStateLogger = function(pc) { + var startTime = OT.$.now(), + finishTime, + suceeded, + states = []; + + var trackState = function() { + var now = OT.$.now(), + lastState = states[states.length-1], + state = {delta: finishTime ? now - finishTime : 0}; + + if (!lastState || lastState.iceConnection !== pc.iceConnectionState) { + state.iceConnectionState = pc.iceConnectionState; + } + + if (!lastState || lastState.signalingState !== pc.signalingState) { + state.signalingState = pc.signalingState; + } + + if (!lastState || lastState.iceGatheringState !== pc.iceGatheringState) { + state.iceGathering = pc.iceGatheringState; + } + OT.debug(state); + states.push(state); + finishTime = now; + }; + + pc.addEventListener('iceconnectionstatechange', trackState, false); + pc.addEventListener('signalingstatechange', trackState, false); + pc.addEventListener('icegatheringstatechange', trackState, false); + + return { + stop: function () { + pc.removeEventListener('iceconnectionstatechange', trackState, false); + pc.removeEventListener('signalingstatechange', trackState, false); + pc.removeEventListener('icegatheringstatechange', trackState, false); + + // @todo The client logging of these states is not currently used, so it's left todo. + + // @todo analyse final state and decide whether the connection was successful + suceeded = true; + + var payload = { + type: 'PeerConnectionWorkflow', + success: suceeded, + startTime: startTime, + finishTime: finishTime, + states: states + }; + + // @todo send client event + OT.debug(payload); + } + }; +}; + +// tb_require('../../helpers/helpers.js') +// tb_require('../../helpers/lib/web_rtc.js') +// tb_require('./connection_state_logger.js') +// tb_require('./ice_candidate_processor.js') +// tb_require('./subscribe_processor.js') +// tb_require('./offer_processor.js') +// tb_require('./get_stats_adapter.js') + +/* global offerProcessor, subscribeProcessor, connectionStateLogger, IceCandidateProcessor */ + +// Normalise these +var NativeRTCSessionDescription; + +if (!OTPlugin.isInstalled()) { + // order is very important: 'RTCSessionDescription' defined in Firefox Nighly but useless + NativeRTCSessionDescription = (window.mozRTCSessionDescription || + window.RTCSessionDescription); +} +else { + NativeRTCSessionDescription = OTPlugin.RTCSessionDescription; +} + + +// Helper function to forward Ice Candidates via +messageDelegate+ +var iceCandidateForwarder = function(messageDelegate) { + return function(event) { + if (event.candidate) { + messageDelegate(OT.Raptor.Actions.CANDIDATE, event.candidate); + } else { + OT.debug('IceCandidateForwarder: No more ICE candidates.'); + } + }; +}; + +/* + * Negotiates a WebRTC PeerConnection. + * + * Responsible for: + * * offer-answer exchange + * * iceCandidates + * * notification of remote streams being added/removed + * + */ +OT.PeerConnection = function(config) { + var _peerConnection, + _peerConnectionCompletionHandlers = [], + _iceProcessor = new IceCandidateProcessor(), + _getStatsAdapter = OT.getStatsAdpater(), + _stateLogger, + _offer, + _answer, + _state = 'new', + _messageDelegates = []; + + + OT.$.eventing(this); + + // if ice servers doesn't exist Firefox will throw an exception. Chrome + // interprets this as 'Use my default STUN servers' whereas FF reads it + // as 'Don't use STUN at all'. *Grumble* + if (!config.iceServers) config.iceServers = []; + + // Private methods + var delegateMessage = OT.$.bind(function(type, messagePayload, uri) { + if (_messageDelegates.length) { + // We actually only ever send to the first delegate. This is because + // each delegate actually represents a Publisher/Subscriber that + // shares a single PeerConnection. If we sent to all delegates it + // would result in each message being processed multiple times by + // each PeerConnection. + _messageDelegates[0](type, messagePayload, uri); + } + }, this), + + // Create and initialise the PeerConnection object. This deals with + // any differences between the various browser implementations and + // our own OTPlugin version. + // + // +completion+ is the function is call once we've either successfully + // created the PeerConnection or on failure. + // + // +localWebRtcStream+ will be null unless the callee is representing + // a publisher. This is an unfortunate implementation limitation + // of OTPlugin, it's not used for vanilla WebRTC. Hopefully this can + // be tidied up later. + // + createPeerConnection = OT.$.bind(function (completion, localWebRtcStream) { + if (_peerConnection) { + completion.call(null, null, _peerConnection); + return; + } + + _peerConnectionCompletionHandlers.push(completion); + + if (_peerConnectionCompletionHandlers.length > 1) { + // The PeerConnection is already being setup, just wait for + // it to be ready. + return; + } + + var pcConstraints = { + optional: [ + {DtlsSrtpKeyAgreement: true} + ] + }; + + OT.debug('Creating peer connection config "' + JSON.stringify(config) + '".'); + + if (!config.iceServers || config.iceServers.length === 0) { + // This should never happen unless something is misconfigured + OT.error('No ice servers present'); + } + + OT.$.createPeerConnection(config, pcConstraints, localWebRtcStream, + attachEventsToPeerConnection); + }, this), + + // An auxiliary function to createPeerConnection. This binds the various event callbacks + // once the peer connection is created. + // + // +err+ will be non-null if an err occured while creating the PeerConnection + // +pc+ will be the PeerConnection object itself. + // + attachEventsToPeerConnection = OT.$.bind(function(err, pc) { + if (err) { + triggerError('Failed to create PeerConnection, exception: ' + + err.toString(), 'NewPeerConnection'); + + _peerConnectionCompletionHandlers = []; + return; + } + + OT.debug('OT attachEventsToPeerConnection'); + _peerConnection = pc; + _stateLogger = connectionStateLogger(_peerConnection); + + _peerConnection.addEventListener('icecandidate', + iceCandidateForwarder(delegateMessage), false); + _peerConnection.addEventListener('addstream', onRemoteStreamAdded, false); + _peerConnection.addEventListener('removestream', onRemoteStreamRemoved, false); + _peerConnection.addEventListener('signalingstatechange', routeStateChanged, false); + + if (_peerConnection.oniceconnectionstatechange !== void 0) { + var failedStateTimer; + _peerConnection.addEventListener('iceconnectionstatechange', function (event) { + if (event.target.iceConnectionState === 'failed') { + if (failedStateTimer) { + clearTimeout(failedStateTimer); + } + + // We wait 5 seconds and make sure that it's still in the failed state + // before we trigger the error. This is because we sometimes see + // 'failed' and then 'connected' afterwards. + failedStateTimer = setTimeout(function () { + if (event.target.iceConnectionState === 'failed') { + triggerError('The stream was unable to connect due to a network error.' + + ' Make sure your connection isn\'t blocked by a firewall.', 'ICEWorkflow'); + } + }, 5000); + } + }, false); + } + + triggerPeerConnectionCompletion(null); + }, this), + + triggerPeerConnectionCompletion = function () { + while (_peerConnectionCompletionHandlers.length) { + _peerConnectionCompletionHandlers.shift().call(null); + } + }, + + // Clean up the Peer Connection and trigger the close event. + // This function can be called safely multiple times, it will + // only trigger the close event once (per PeerConnection object) + tearDownPeerConnection = function() { + // Our connection is dead, stop processing ICE candidates + if (_iceProcessor) _iceProcessor.setPeerConnection(null); + if (_stateLogger) _stateLogger.stop(); + + qos.stopCollecting(); + + if (_peerConnection !== null) { + if (_peerConnection.destroy) { + // OTPlugin defines a destroy method on PCs. This allows + // the plugin to release any resources that it's holding. + _peerConnection.destroy(); + } + + _peerConnection = null; + this.trigger('close'); + } + }, + + routeStateChanged = OT.$.bind(function() { + var newState = _peerConnection.signalingState; + + if (newState && newState !== _state) { + _state = newState; + OT.debug('PeerConnection.stateChange: ' + _state); + + switch(_state) { + case 'closed': + tearDownPeerConnection.call(this); + break; + } + } + }, this), + + qosCallback = OT.$.bind(function(parsedStats) { + this.trigger('qos', parsedStats); + }, this), + + getRemoteStreams = function() { + var streams; + + if (_peerConnection.getRemoteStreams) { + streams = _peerConnection.getRemoteStreams(); + } else if (_peerConnection.remoteStreams) { + streams = _peerConnection.remoteStreams; + } else { + throw new Error('Invalid Peer Connection object implements no ' + + 'method for retrieving remote streams'); + } + + // Force streams to be an Array, rather than a 'Sequence' object, + // which is browser dependent and does not behaviour like an Array + // in every case. + return Array.prototype.slice.call(streams); + }, + + /// PeerConnection signaling + onRemoteStreamAdded = OT.$.bind(function(event) { + this.trigger('streamAdded', event.stream); + }, this), + + onRemoteStreamRemoved = OT.$.bind(function(event) { + this.trigger('streamRemoved', event.stream); + }, this), + + // ICE Negotiation messages + + + // Relays a SDP payload (+sdp+), that is part of a message of type +messageType+ + // via the registered message delegators + relaySDP = function(messageType, sdp, uri) { + delegateMessage(messageType, sdp, uri); + }, + + + // Process an offer that + processOffer = function(message) { + var offer = new NativeRTCSessionDescription({type: 'offer', sdp: message.content.sdp}), + + // Relays +answer+ Answer + relayAnswer = function(answer) { + _iceProcessor.setPeerConnection(_peerConnection); + _iceProcessor.processPending(); + relaySDP(OT.Raptor.Actions.ANSWER, answer); + + qos.startCollecting(_peerConnection); + }, + + reportError = function(message, errorReason, prefix) { + triggerError('PeerConnection.offerProcessor ' + message + ': ' + + errorReason, prefix); + }; + + createPeerConnection(function() { + offerProcessor( + _peerConnection, + offer, + relayAnswer, + reportError + ); + }); + }, + + processAnswer = function(message) { + if (!message.content.sdp) { + OT.error('PeerConnection.processMessage: Weird answer message, no SDP.'); + return; + } + + _answer = new NativeRTCSessionDescription({type: 'answer', sdp: message.content.sdp}); + + _peerConnection.setRemoteDescription(_answer, + function () { + OT.debug('setRemoteDescription Success'); + }, function (errorReason) { + triggerError('Error while setting RemoteDescription ' + errorReason, + 'SetRemoteDescription'); + }); + + _iceProcessor.setPeerConnection(_peerConnection); + _iceProcessor.processPending(); + + qos.startCollecting(_peerConnection); + }, + + processSubscribe = function(message) { + OT.debug('PeerConnection.processSubscribe: Sending offer to subscriber.'); + + if (!_peerConnection) { + // TODO(rolly) I need to examine whether this can + // actually happen. If it does happen in the short + // term, I want it to be noisy. + throw new Error('PeerConnection broke!'); + } + + createPeerConnection(function() { + subscribeProcessor( + _peerConnection, + + // Success: Relay Offer + function(offer) { + _offer = offer; + relaySDP(OT.Raptor.Actions.OFFER, _offer, message.uri); + }, + + // Failure + function(message, errorReason, prefix) { + triggerError('PeerConnection.subscribeProcessor ' + message + ': ' + + errorReason, prefix); + } + ); + }); + }, + + triggerError = OT.$.bind(function(errorReason, prefix) { + OT.error(errorReason); + this.trigger('error', errorReason, prefix); + }, this); + + this.addLocalStream = function(webRTCStream) { + createPeerConnection(function() { + _peerConnection.addStream(webRTCStream); + }, webRTCStream); + }; + + this.disconnect = function() { + _iceProcessor = null; + + if (_peerConnection && + _peerConnection.signalingState && + _peerConnection.signalingState.toLowerCase() !== 'closed') { + + _peerConnection.close(); + + if (OT.$.env.name === 'Firefox') { + // FF seems to never go into the closed signalingState when the close + // method is called on a PeerConnection. This means that we need to call + // our cleanup code manually. + // + // * https://bugzilla.mozilla.org/show_bug.cgi?id=989936 + // + OT.$.callAsync(OT.$.bind(tearDownPeerConnection, this)); + } + } + + this.off(); + }; + + this.processMessage = function(type, message) { + OT.debug('PeerConnection.processMessage: Received ' + + type + ' from ' + message.fromAddress); + + OT.debug(message); + + switch(type) { + case 'generateoffer': + processSubscribe.call(this, message); + break; + + case 'offer': + processOffer.call(this, message); + break; + + case 'answer': + case 'pranswer': + processAnswer.call(this, message); + break; + + case 'candidate': + _iceProcessor.process(message); + break; + + default: + OT.debug('PeerConnection.processMessage: Received an unexpected message of type ' + + type + ' from ' + message.fromAddress + ': ' + JSON.stringify(message)); + } + + return this; + }; + + this.setIceServers = function (iceServers) { + if (iceServers) { + config.iceServers = iceServers; + } + }; + + this.registerMessageDelegate = function(delegateFn) { + return _messageDelegates.push(delegateFn); + }; + + this.unregisterMessageDelegate = function(delegateFn) { + var index = OT.$.arrayIndexOf(_messageDelegates, delegateFn); + + if ( index !== -1 ) { + _messageDelegates.splice(index, 1); + } + return _messageDelegates.length; + }; + + this.remoteStreams = function() { + return _peerConnection ? getRemoteStreams() : []; + }; + + this.getStats = function(callback) { + createPeerConnection(function() { + _getStatsAdapter(_peerConnection, callback); + }); + }; + + var qos = new OT.PeerConnection.QOS(qosCallback); +}; + +// tb_require('../../helpers/helpers.js') +// tb_require('./peer_connection.js') + +// +// There are three implementations of stats parsing in this file. +// 1. For Chrome: Chrome is currently using an older version of the API +// 2. For OTPlugin: The plugin is using a newer version of the API that +// exists in the latest WebRTC codebase +// 3. For Firefox: FF is using a version that looks a lot closer to the +// current spec. +// +// I've attempted to keep the three implementations from sharing any code, +// accordingly you'll notice a bunch of duplication between the three. +// +// This is acceptable as the goal is to be able to remove each implementation +// as it's no longer needed without any risk of affecting the others. If there +// was shared code between them then each removal would require an audit of +// all the others. +// +// +!(function() { + + /// + // Get Stats using the older API. Used by all current versions + // of Chrome. + // + var parseStatsOldAPI = function parseStatsOldAPI (peerConnection, + prevStats, + currentStats, + completion) { + + /* this parses a result if there it contains the video bitrate */ + var parseVideoStats = function (result) { + if (result.stat('googFrameRateSent')) { + currentStats.videoSentBytes = Number(result.stat('bytesSent')); + currentStats.videoSentPackets = Number(result.stat('packetsSent')); + currentStats.videoSentPacketsLost = Number(result.stat('packetsLost')); + currentStats.videoRtt = Number(result.stat('googRtt')); + currentStats.videoFrameRate = Number(result.stat('googFrameRateInput')); + currentStats.videoWidth = Number(result.stat('googFrameWidthSent')); + currentStats.videoHeight = Number(result.stat('googFrameHeightSent')); + currentStats.videoCodec = result.stat('googCodecName'); + } else if (result.stat('googFrameRateReceived')) { + currentStats.videoRecvBytes = Number(result.stat('bytesReceived')); + currentStats.videoRecvPackets = Number(result.stat('packetsReceived')); + currentStats.videoRecvPacketsLost = Number(result.stat('packetsLost')); + currentStats.videoFrameRate = Number(result.stat('googFrameRateOutput')); + currentStats.videoWidth = Number(result.stat('googFrameWidthReceived')); + currentStats.videoHeight = Number(result.stat('googFrameHeightReceived')); + currentStats.videoCodec = result.stat('googCodecName'); + } + return null; + }, + + parseAudioStats = function (result) { + if (result.stat('audioInputLevel')) { + currentStats.audioSentPackets = Number(result.stat('packetsSent')); + currentStats.audioSentPacketsLost = Number(result.stat('packetsLost')); + currentStats.audioSentBytes = Number(result.stat('bytesSent')); + currentStats.audioCodec = result.stat('googCodecName'); + currentStats.audioRtt = Number(result.stat('googRtt')); + } else if (result.stat('audioOutputLevel')) { + currentStats.audioRecvPackets = Number(result.stat('packetsReceived')); + currentStats.audioRecvPacketsLost = Number(result.stat('packetsLost')); + currentStats.audioRecvBytes = Number(result.stat('bytesReceived')); + currentStats.audioCodec = result.stat('googCodecName'); + } + }, + + parseStatsReports = function (stats) { + if (stats.result) { + var resultList = stats.result(); + for (var resultIndex = 0; resultIndex < resultList.length; resultIndex++) { + var result = resultList[resultIndex]; + + if (result.stat) { + + if(result.stat('googActiveConnection') === 'true') { + currentStats.localCandidateType = result.stat('googLocalCandidateType'); + currentStats.remoteCandidateType = result.stat('googRemoteCandidateType'); + currentStats.transportType = result.stat('googTransportType'); + } + + parseAudioStats(result); + parseVideoStats(result); + } + } + } + + completion(null, currentStats); + }; + + peerConnection.getStats(parseStatsReports); + }; + + /// + // Get Stats for the OT Plugin, newer than Chromes version, but + // still not in sync with the spec. + // + var parseStatsOTPlugin = function parseStatsOTPlugin (peerConnection, + prevStats, + currentStats, + completion) { + + var onStatsError = function onStatsError (error) { + completion(error); + }, + + /// + // From the Audio Tracks + // * avgAudioBitrate + // * audioBytesTransferred + // + parseAudioStats = function (statsReport) { + var lastBytesSent = prevStats.audioBytesTransferred || 0, + transferDelta; + + if (statsReport.audioInputLevel) { + currentStats.audioSentBytes = Number(statsReport.bytesSent); + currentStats.audioSentPackets = Number(statsReport.packetsSent); + currentStats.audioSentPacketsLost = Number(statsReport.packetsLost); + currentStats.audioRtt = Number(statsReport.googRtt); + currentStats.audioCodec = statsReport.googCodecName; + } + else if (statsReport.audioOutputLevel) { + currentStats.audioBytesTransferred = Number(statsReport.bytesReceived); + currentStats.audioCodec = statsReport.googCodecName; + } + + if (currentStats.audioBytesTransferred) { + transferDelta = currentStats.audioBytesTransferred - lastBytesSent; + currentStats.avgAudioBitrate = Math.round(transferDelta * 8 / currentStats.period); + } + }, + + /// + // From the Video Tracks + // * frameRate + // * avgVideoBitrate + // * videoBytesTransferred + // + parseVideoStats = function (statsReport) { + + var lastBytesSent = prevStats.videoBytesTransferred || 0, + transferDelta; + + if (statsReport.googFrameHeightSent) { + currentStats.videoSentBytes = Number(statsReport.bytesSent); + currentStats.videoSentPackets = Number(statsReport.packetsSent); + currentStats.videoSentPacketsLost = Number(statsReport.packetsLost); + currentStats.videoRtt = Number(statsReport.googRtt); + currentStats.videoCodec = statsReport.googCodecName; + currentStats.videoWidth = Number(statsReport.googFrameWidthSent); + currentStats.videoHeight = Number(statsReport.googFrameHeightSent); + } + else if (statsReport.googFrameHeightReceived) { + currentStats.videoRecvBytes = Number(statsReport.bytesReceived); + currentStats.videoRecvPackets = Number(statsReport.packetsReceived); + currentStats.videoRecvPacketsLost = Number(statsReport.packetsLost); + currentStats.videoRtt = Number(statsReport.googRtt); + currentStats.videoCodec = statsReport.googCodecName; + currentStats.videoWidth = Number(statsReport.googFrameWidthReceived); + currentStats.videoHeight = Number(statsReport.googFrameHeightReceived); + } + + if (currentStats.videoBytesTransferred) { + transferDelta = currentStats.videoBytesTransferred - lastBytesSent; + currentStats.avgVideoBitrate = Math.round(transferDelta * 8 / currentStats.period); + } + + if (statsReport.googFrameRateSent) { + currentStats.videoFrameRate = Number(statsReport.googFrameRateSent); + } else if (statsReport.googFrameRateReceived) { + currentStats.videoFrameRate = Number(statsReport.googFrameRateReceived); + } + }, + + isStatsForVideoTrack = function(statsReport) { + return statsReport.googFrameHeightSent !== void 0 || + statsReport.googFrameHeightReceived !== void 0 || + currentStats.videoBytesTransferred !== void 0 || + statsReport.googFrameRateSent !== void 0; + }, + + isStatsForIceCandidate = function(statsReport) { + return statsReport.googActiveConnection === 'true'; + }; + + peerConnection.getStats(null, function(statsReports) { + statsReports.forEach(function(statsReport) { + if (isStatsForIceCandidate(statsReport)) { + currentStats.localCandidateType = statsReport.googLocalCandidateType; + currentStats.remoteCandidateType = statsReport.googRemoteCandidateType; + currentStats.transportType = statsReport.googTransportType; + } + else if (isStatsForVideoTrack(statsReport)) { + parseVideoStats(statsReport); + } + else { + parseAudioStats(statsReport); + } + }); + + completion(null, currentStats); + }, onStatsError); + }; + + + /// + // Get Stats using the newer API. + // + var parseStatsNewAPI = function parseStatsNewAPI (peerConnection, + prevStats, + currentStats, + completion) { + + var onStatsError = function onStatsError (error) { + completion(error); + }, + + parseAudioStats = function (result) { + if (result.type==='outboundrtp') { + currentStats.audioSentPackets = result.packetsSent; + currentStats.audioSentPacketsLost = result.packetsLost; + currentStats.audioSentBytes = result.bytesSent; + } else if (result.type==='inboundrtp') { + currentStats.audioRecvPackets = result.packetsReceived; + currentStats.audioRecvPacketsLost = result.packetsLost; + currentStats.audioRecvBytes = result.bytesReceived; + } + }, + + parseVideoStats = function (result) { + if (result.type==='outboundrtp') { + currentStats.videoSentPackets = result.packetsSent; + currentStats.videoSentPacketsLost = result.packetsLost; + currentStats.videoSentBytes = result.bytesSent; + } else if (result.type==='inboundrtp') { + currentStats.videoRecvPackets = result.packetsReceived; + currentStats.videoRecvPacketsLost = result.packetsLost; + currentStats.videoRecvBytes = result.bytesReceived; + } + }; + + peerConnection.getStats(null, function(stats) { + + for (var key in stats) { + if (stats.hasOwnProperty(key) && + (stats[key].type === 'outboundrtp' || stats[key].type === 'inboundrtp')) { + var res = stats[key]; + + if (res.id.indexOf('audio') !== -1) { + parseAudioStats(res); + } else if (res.id.indexOf('video') !== -1) { + parseVideoStats(res); + } + } + } + + completion(null, currentStats); + }, onStatsError); + }; + + + var parseQOS = function (peerConnection, prevStats, currentStats, completion) { + if (OTPlugin.isInstalled()) { + parseQOS = parseStatsOTPlugin; + return parseStatsOTPlugin(peerConnection, prevStats, currentStats, completion); + } + else if (OT.$.env.name === 'Firefox' && OT.$.env.version >= 27) { + parseQOS = parseStatsNewAPI; + return parseStatsNewAPI(peerConnection, prevStats, currentStats, completion); + } + else { + parseQOS = parseStatsOldAPI; + return parseStatsOldAPI(peerConnection, prevStats, currentStats, completion); + } + }; + + OT.PeerConnection.QOS = function (qosCallback) { + var _creationTime = OT.$.now(), + _peerConnection; + + var calculateQOS = OT.$.bind(function calculateQOS (prevStats) { + if (!_peerConnection) { + // We don't have a PeerConnection yet, or we did and + // it's been closed. Either way we're done. + return; + } + + var now = OT.$.now(); + + var currentStats = { + timeStamp: now, + duration: Math.round(now - _creationTime), + period: (now - prevStats.timeStamp) / 1000 + }; + + var onParsedStats = function (err, parsedStats) { + if (err) { + OT.error('Failed to Parse QOS Stats: ' + JSON.stringify(err)); + return; + } + + qosCallback(parsedStats, prevStats); + + // Recalculate the stats + setTimeout(OT.$.bind(calculateQOS, null, parsedStats), OT.PeerConnection.QOS.INTERVAL); + }; + + parseQOS(_peerConnection, prevStats, currentStats, onParsedStats); + }, this); + + + this.startCollecting = function (peerConnection) { + if (!peerConnection || !peerConnection.getStats) { + // It looks like this browser doesn't support getStats + // Bail. + return; + } + + _peerConnection = peerConnection; + + calculateQOS({ + timeStamp: OT.$.now() + }); + }; + + this.stopCollecting = function () { + _peerConnection = null; + }; + }; + + // Recalculate the stats in 30 sec + OT.PeerConnection.QOS.INTERVAL = 30000; +})(); + +// tb_require('../../helpers/helpers.js') +// tb_require('./peer_connection.js') + +OT.PeerConnections = (function() { + var _peerConnections = {}; + + return { + add: function(remoteConnection, streamId, config) { + var key = remoteConnection.id + '_' + streamId, + ref = _peerConnections[key]; + + if (!ref) { + ref = _peerConnections[key] = { + count: 0, + pc: new OT.PeerConnection(config) + }; + } + + // increase the PCs ref count by 1 + ref.count += 1; + + return ref.pc; + }, + + remove: function(remoteConnection, streamId) { + var key = remoteConnection.id + '_' + streamId, + ref = _peerConnections[key]; + + if (ref) { + ref.count -= 1; + + if (ref.count === 0) { + ref.pc.disconnect(); + delete _peerConnections[key]; + } + } + } + }; +})(); +// tb_require('../../helpers/helpers.js') +// tb_require('../messaging/raptor/raptor.js') +// tb_require('./peer_connections.js') + +/* + * Abstracts PeerConnection related stuff away from OT.Subscriber. + * + * Responsible for: + * * setting up the underlying PeerConnection (delegates to OT.PeerConnections) + * * triggering a connected event when the Peer connection is opened + * * triggering a disconnected event when the Peer connection is closed + * * creating a video element when a stream is added + * * responding to stream removed intelligently + * * providing a destroy method + * * providing a processMessage method + * + * Once the PeerConnection is connected and the video element playing it + * triggers the connected event + * + * Triggers the following events + * * connected + * * disconnected + * * remoteStreamAdded + * * remoteStreamRemoved + * * error + * + */ + +OT.SubscriberPeerConnection = function(remoteConnection, session, stream, + subscriber, properties) { + var _peerConnection, + _destroyed = false, + _hasRelayCandidates = false, + _onPeerClosed, + _onRemoteStreamAdded, + _onRemoteStreamRemoved, + _onPeerError, + _relayMessageToPeer, + _setEnabledOnStreamTracksCurry, + _onQOS; + + // Private + _onPeerClosed = function() { + this.destroy(); + this.trigger('disconnected', this); + }; + + _onRemoteStreamAdded = function(remoteRTCStream) { + this.trigger('remoteStreamAdded', remoteRTCStream, this); + }; + + _onRemoteStreamRemoved = function(remoteRTCStream) { + this.trigger('remoteStreamRemoved', remoteRTCStream, this); + }; + + // Note: All Peer errors are fatal right now. + _onPeerError = function(errorReason, prefix) { + this.trigger('error', null, errorReason, this, prefix); + }; + + _relayMessageToPeer = OT.$.bind(function(type, payload) { + if (!_hasRelayCandidates){ + var extractCandidates = type === OT.Raptor.Actions.CANDIDATE || + type === OT.Raptor.Actions.OFFER || + type === OT.Raptor.Actions.ANSWER || + type === OT.Raptor.Actions.PRANSWER ; + + if (extractCandidates) { + var message = (type === OT.Raptor.Actions.CANDIDATE) ? payload.candidate : payload.sdp; + _hasRelayCandidates = message.indexOf('typ relay') !== -1; + } + } + + switch(type) { + case OT.Raptor.Actions.ANSWER: + case OT.Raptor.Actions.PRANSWER: + this.trigger('connected'); + + session._.jsepAnswerP2p(stream.id, subscriber.widgetId, payload.sdp); + break; + + case OT.Raptor.Actions.OFFER: + session._.jsepOfferP2p(stream.id, subscriber.widgetId, payload.sdp); + break; + + case OT.Raptor.Actions.CANDIDATE: + session._.jsepCandidateP2p(stream.id, subscriber.widgetId, payload); + break; + } + }, this); + + // Helper method used by subscribeToAudio/subscribeToVideo + _setEnabledOnStreamTracksCurry = function(isVideo) { + var method = 'get' + (isVideo ? 'Video' : 'Audio') + 'Tracks'; + + return function(enabled) { + var remoteStreams = _peerConnection.remoteStreams(), + tracks, + stream; + + if (remoteStreams.length === 0 || !remoteStreams[0][method]) { + // either there is no remote stream or we are in a browser that doesn't + // expose the media tracks (Firefox) + return; + } + + for (var i=0, num=remoteStreams.length; i 0) { + OT.$.forEach(_peerConnection.remoteStreams(), _onRemoteStreamAdded, this); + } else if (numDelegates === 1) { + // We only bother with the PeerConnection negotiation if we don't already + // have a remote stream. + + var channelsToSubscribeTo; + + if (properties.subscribeToVideo || properties.subscribeToAudio) { + var audio = stream.getChannelsOfType('audio'), + video = stream.getChannelsOfType('video'); + + channelsToSubscribeTo = OT.$.map(audio, function(channel) { + return { + id: channel.id, + type: channel.type, + active: properties.subscribeToAudio + }; + }).concat(OT.$.map(video, function(channel) { + return { + id: channel.id, + type: channel.type, + active: properties.subscribeToVideo, + restrictFrameRate: properties.restrictFrameRate !== void 0 ? + properties.restrictFrameRate : false + }; + })); + } + + session._.subscriberCreate(stream, subscriber, channelsToSubscribeTo, + OT.$.bind(function(err, message) { + if (err) { + this.trigger('error', err.message, this, 'Subscribe'); + } + if (_peerConnection) { + _peerConnection.setIceServers(OT.Raptor.parseIceServers(message)); + } + }, this)); + } + }; +}; + +// tb_require('../../helpers/helpers.js') +// tb_require('../messaging/raptor/raptor.js') +// tb_require('./peer_connections.js') + + +/* + * Abstracts PeerConnection related stuff away from OT.Publisher. + * + * Responsible for: + * * setting up the underlying PeerConnection (delegates to OT.PeerConnections) + * * triggering a connected event when the Peer connection is opened + * * triggering a disconnected event when the Peer connection is closed + * * providing a destroy method + * * providing a processMessage method + * + * Once the PeerConnection is connected and the video element playing it triggers + * the connected event + * + * Triggers the following events + * * connected + * * disconnected + */ +OT.PublisherPeerConnection = function(remoteConnection, session, streamId, webRTCStream) { + var _peerConnection, + _hasRelayCandidates = false, + _subscriberId = session._.subscriberMap[remoteConnection.id + '_' + streamId], + _onPeerClosed, + _onPeerError, + _relayMessageToPeer, + _onQOS; + + // Private + _onPeerClosed = function() { + this.destroy(); + this.trigger('disconnected', this); + }; + + // Note: All Peer errors are fatal right now. + _onPeerError = function(errorReason, prefix) { + this.trigger('error', null, errorReason, this, prefix); + this.destroy(); + }; + + _relayMessageToPeer = OT.$.bind(function(type, payload, uri) { + if (!_hasRelayCandidates){ + var extractCandidates = type === OT.Raptor.Actions.CANDIDATE || + type === OT.Raptor.Actions.OFFER || + type === OT.Raptor.Actions.ANSWER || + type === OT.Raptor.Actions.PRANSWER ; + + if (extractCandidates) { + var message = (type === OT.Raptor.Actions.CANDIDATE) ? payload.candidate : payload.sdp; + _hasRelayCandidates = message.indexOf('typ relay') !== -1; + } + } + + switch(type) { + case OT.Raptor.Actions.ANSWER: + case OT.Raptor.Actions.PRANSWER: + if (session.sessionInfo.p2pEnabled) { + session._.jsepAnswerP2p(streamId, _subscriberId, payload.sdp); + } else { + session._.jsepAnswer(streamId, payload.sdp); + } + + break; + + case OT.Raptor.Actions.OFFER: + this.trigger('connected'); + session._.jsepOffer(uri, payload.sdp); + + break; + + case OT.Raptor.Actions.CANDIDATE: + if (session.sessionInfo.p2pEnabled) { + session._.jsepCandidateP2p(streamId, _subscriberId, payload); + + } else { + session._.jsepCandidate(streamId, payload); + } + } + }, this); + + _onQOS = OT.$.bind(function _onQOS (parsedStats, prevStats) { + this.trigger('qos', remoteConnection, parsedStats, prevStats); + }, this); + + OT.$.eventing(this); + + // Public + this.destroy = function() { + // Clean up our PeerConnection + if (_peerConnection) { + _peerConnection.off(); + OT.PeerConnections.remove(remoteConnection, streamId); + } + + _peerConnection = null; + }; + + this.processMessage = function(type, message) { + _peerConnection.processMessage(type, message); + }; + + // Init + this.init = function(iceServers) { + _peerConnection = OT.PeerConnections.add(remoteConnection, streamId, { + iceServers: iceServers + }); + + _peerConnection.on({ + close: _onPeerClosed, + error: _onPeerError, + qos: _onQOS + }, this); + + _peerConnection.registerMessageDelegate(_relayMessageToPeer); + _peerConnection.addLocalStream(webRTCStream); + + this.remoteConnection = function() { + return remoteConnection; + }; + + this.hasRelayCandidates = function() { + return _hasRelayCandidates; + }; + + }; +}; + + +// tb_require('../helpers.js') +// tb_require('./web_rtc.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT, OTPlugin */ + +var videoContentResizesMixin = function(self, domElement) { + + var width = domElement.videoWidth, + height = domElement.videoHeight, + stopped = true; + + function actor() { + if (stopped) { + return; + } + if (width !== domElement.videoWidth || height !== domElement.videoHeight) { + self.trigger('videoDimensionsChanged', + { width: width, height: height }, + { width: domElement.videoWidth, height: domElement.videoHeight } + ); + width = domElement.videoWidth; + height = domElement.videoHeight; + } + waiter(); + } + + function waiter() { + self.whenTimeIncrements(function() { + window.requestAnimationFrame(actor); + }); + } + + self.startObservingSize = function() { + stopped = false; + waiter(); + }; + + self.stopObservingSize = function() { + stopped = true; + }; + +}; + +(function(window) { + + var VideoOrientationTransforms = { + 0: 'rotate(0deg)', + 270: 'rotate(90deg)', + 90: 'rotate(-90deg)', + 180: 'rotate(180deg)' + }; + + OT.VideoOrientation = { + ROTATED_NORMAL: 0, + ROTATED_LEFT: 270, + ROTATED_RIGHT: 90, + ROTATED_UPSIDE_DOWN: 180 + }; + + var DefaultAudioVolume = 50; + + var DEGREE_TO_RADIANS = Math.PI * 2 / 360; + + // + // + // var _videoElement = new OT.VideoElement({ + // fallbackText: 'blah' + // }, errorHandler); + // + // _videoElement.bindToStream(webRtcStream, completion); // => VideoElement + // _videoElement.appendTo(DOMElement) // => VideoElement + // + // _videoElement.domElement // => DomNode + // + // _videoElement.imgData // => PNG Data string + // + // _videoElement.orientation = OT.VideoOrientation.ROTATED_LEFT; + // + // _videoElement.unbindStream(); + // _videoElement.destroy() // => Completely cleans up and + // removes the video element + // + // + OT.VideoElement = function(/* optional */ options/*, optional errorHandler*/) { + var _options = OT.$.defaults( options && !OT.$.isFunction(options) ? options : {}, { + fallbackText: 'Sorry, Web RTC is not available in your browser' + }), + + errorHandler = OT.$.isFunction(arguments[arguments.length-1]) ? + arguments[arguments.length-1] : void 0, + + orientationHandler = OT.$.bind(function(orientation) { + this.trigger('orientationChanged', orientation); + }, this), + + _videoElement = OTPlugin.isInstalled() ? + new PluginVideoElement(_options, errorHandler, orientationHandler) : + new NativeDOMVideoElement(_options, errorHandler, orientationHandler), + _streamBound = false, + _stream, + _preInitialisedVolue; + + OT.$.eventing(this); + + _videoElement.on('videoDimensionsChanged', OT.$.bind(function(oldValue, newValue) { + this.trigger('videoDimensionsChanged', oldValue, newValue); + }, this)); + + _videoElement.on('mediaStopped', OT.$.bind(function() { + this.trigger('mediaStopped'); + }, this)); + + // Public Properties + OT.$.defineProperties(this, { + + domElement: { + get: function() { + return _videoElement.domElement(); + } + }, + + videoWidth: { + get: function() { + return _videoElement['video' + (this.isRotated() ? 'Height' : 'Width')](); + } + }, + + videoHeight: { + get: function() { + return _videoElement['video' + (this.isRotated() ? 'Width' : 'Height')](); + } + }, + + aspectRatio: { + get: function() { + return (this.videoWidth() + 0.0) / this.videoHeight(); + } + }, + + isRotated: { + get: function() { + return _videoElement.isRotated(); + } + }, + + orientation: { + get: function() { + return _videoElement.orientation(); + }, + set: function(orientation) { + _videoElement.orientation(orientation); + } + }, + + audioChannelType: { + get: function() { + return _videoElement.audioChannelType(); + }, + set: function(type) { + _videoElement.audioChannelType(type); + } + } + }); + + // Public Methods + + this.imgData = function() { + return _videoElement.imgData(); + }; + + this.appendTo = function(parentDomElement) { + _videoElement.appendTo(parentDomElement); + return this; + }; + + this.bindToStream = function(webRtcStream, completion) { + _streamBound = false; + _stream = webRtcStream; + + _videoElement.bindToStream(webRtcStream, OT.$.bind(function(err) { + if (err) { + completion(err); + return; + } + + _streamBound = true; + + if (_preInitialisedVolue) { + this.setAudioVolume(_preInitialisedVolue); + _preInitialisedVolue = null; + } + + _videoElement.on('aspectRatioAvailable', + OT.$.bind(this.trigger, this, 'aspectRatioAvailable')); + + completion(null); + }, this)); + + return this; + }; + + this.unbindStream = function() { + if (!_stream) return this; + + _stream = null; + _videoElement.unbindStream(); + return this; + }; + + this.setAudioVolume = function (value) { + if (_streamBound) _videoElement.setAudioVolume( OT.$.roundFloat(value / 100, 2) ); + else _preInitialisedVolue = value; + + return this; + }; + + this.getAudioVolume = function () { + if (_streamBound) return parseInt(_videoElement.getAudioVolume() * 100, 10); + else return _preInitialisedVolue || 50; + }; + + + this.whenTimeIncrements = function (callback, context) { + _videoElement.whenTimeIncrements(callback, context); + return this; + }; + + this.onRatioAvailable = function(callabck) { + _videoElement.onRatioAvailable(callabck) ; + return this; + }; + + this.destroy = function () { + // unbind all events so they don't fire after the object is dead + this.off(); + + _videoElement.destroy(); + return void 0; + }; + }; + + var PluginVideoElement = function PluginVideoElement (options, + errorHandler, + orientationChangedHandler) { + var _videoProxy, + _parentDomElement, + _ratioAvailable = false, + _ratioAvailableListeners = []; + + OT.$.eventing(this); + + canBeOrientatedMixin(this, + function() { return _videoProxy.domElement; }, + orientationChangedHandler); + + /// Public methods + + this.domElement = function() { + return _videoProxy ? _videoProxy.domElement : void 0; + }; + + this.videoWidth = function() { + return _videoProxy ? _videoProxy.getVideoWidth() : void 0; + }; + + this.videoHeight = function() { + return _videoProxy ? _videoProxy.getVideoHeight() : void 0; + }; + + this.imgData = function() { + return _videoProxy ? _videoProxy.getImgData() : null; + }; + + // Append the Video DOM element to a parent node + this.appendTo = function(parentDomElement) { + _parentDomElement = parentDomElement; + return this; + }; + + // Bind a stream to the video element. + this.bindToStream = function(webRtcStream, completion) { + if (!_parentDomElement) { + completion('The VideoElement must attached to a DOM node before a stream can be bound'); + return; + } + + _videoProxy = webRtcStream._.render(); + _videoProxy.appendTo(_parentDomElement); + _videoProxy.show(function(error) { + + if (!error) { + _ratioAvailable = true; + var listener; + while ((listener = _ratioAvailableListeners.shift())) { + listener(); + } + } + + completion(error); + }); + + return this; + }; + + // Unbind the currently bound stream from the video element. + this.unbindStream = function() { + // TODO: some way to tell OTPlugin to release that stream and controller + + if (_videoProxy) { + _videoProxy.destroy(); + _parentDomElement = null; + _videoProxy = null; + } + + return this; + }; + + this.setAudioVolume = function(value) { + if (_videoProxy) _videoProxy.setVolume(value); + }; + + this.getAudioVolume = function() { + // Return the actual volume of the DOM element + if (_videoProxy) return _videoProxy.getVolume(); + return DefaultAudioVolume; + }; + + // see https://wiki.mozilla.org/WebAPI/AudioChannels + // The audioChannelType is not currently supported in the plugin. + this.audioChannelType = function(/* type */) { + return 'unknown'; + }; + + this.whenTimeIncrements = function(callback, context) { + // exists for compatibility with NativeVideoElement + OT.$.callAsync(OT.$.bind(callback, context)); + }; + + this.onRatioAvailable = function(callback) { + if(_ratioAvailable) { + callback(); + } else { + _ratioAvailableListeners.push(callback); + } + }; + + this.destroy = function() { + this.unbindStream(); + + return void 0; + }; + }; + + + var NativeDOMVideoElement = function NativeDOMVideoElement (options, + errorHandler, + orientationChangedHandler) { + var _domElement, + _videoElementMovedWarning = false; + + OT.$.eventing(this); + + /// Private API + var _onVideoError = OT.$.bind(function(event) { + var reason = 'There was an unexpected problem with the Video Stream: ' + + videoElementErrorCodeToStr(event.target.error.code); + errorHandler(reason, this, 'VideoElement'); + }, this), + + // The video element pauses itself when it's reparented, this is + // unfortunate. This function plays the video again and is triggered + // on the pause event. + _playVideoOnPause = function() { + if(!_videoElementMovedWarning) { + OT.warn('Video element paused, auto-resuming. If you intended to do this, ' + + 'use publishVideo(false) or subscribeToVideo(false) instead.'); + + _videoElementMovedWarning = true; + } + + _domElement.play(); + }; + + + _domElement = createNativeVideoElement(options.fallbackText, options.attributes); + + // dirty but it is the only way right now to get the aspect ratio in FF + // any other event is triggered too early + var ratioAvailable = false; + var ratioAvailableListeners = []; + _domElement.addEventListener('timeupdate', function timeupdateHandler() { + var aspectRatio = _domElement.videoWidth / _domElement.videoHeight; + if (!isNaN(aspectRatio)) { + _domElement.removeEventListener('timeupdate', timeupdateHandler); + ratioAvailable = true; + var listener; + while ((listener = ratioAvailableListeners.shift())) { + listener(); + } + } + }); + + _domElement.addEventListener('pause', _playVideoOnPause); + + videoContentResizesMixin(this, _domElement); + + canBeOrientatedMixin(this, function() { return _domElement; }, orientationChangedHandler); + + /// Public methods + + this.domElement = function() { + return _domElement; + }; + + this.videoWidth = function() { + return _domElement.videoWidth; + }; + + this.videoHeight = function() { + return _domElement.videoHeight; + }; + + this.imgData = function() { + var canvas = OT.$.createElement('canvas', { + width: _domElement.videoWidth, + height: _domElement.videoHeight, + style: { display: 'none' } + }); + + document.body.appendChild(canvas); + try { + canvas.getContext('2d').drawImage(_domElement, 0, 0, canvas.width, canvas.height); + } catch(err) { + OT.warn('Cannot get image data yet'); + return null; + } + var imgData = canvas.toDataURL('image/png'); + + OT.$.removeElement(canvas); + + return OT.$.trim(imgData.replace('data:image/png;base64,', '')); + }; + + // Append the Video DOM element to a parent node + this.appendTo = function(parentDomElement) { + parentDomElement.appendChild(_domElement); + return this; + }; + + // Bind a stream to the video element. + this.bindToStream = function(webRtcStream, completion) { + var _this = this; + bindStreamToNativeVideoElement(_domElement, webRtcStream, function(err) { + if (err) { + completion(err); + return; + } + + _this.startObservingSize(); + + webRtcStream.onended = function() { + _this.trigger('mediaStopped', this); + }; + + + _domElement.addEventListener('error', _onVideoError, false); + completion(null); + }); + + return this; + }; + + + // Unbind the currently bound stream from the video element. + this.unbindStream = function() { + if (_domElement) { + unbindNativeStream(_domElement); + } + + this.stopObservingSize(); + + return this; + }; + + this.setAudioVolume = function(value) { + if (_domElement) _domElement.volume = value; + }; + + this.getAudioVolume = function() { + // Return the actual volume of the DOM element + if (_domElement) return _domElement.volume; + return DefaultAudioVolume; + }; + + // see https://wiki.mozilla.org/WebAPI/AudioChannels + // The audioChannelType is currently only available in Firefox. This property returns + // "unknown" in other browser. The related HTML tag attribute is "mozaudiochannel" + this.audioChannelType = function(type) { + if (type !== void 0) { + _domElement.mozAudioChannelType = type; + } + + if ('mozAudioChannelType' in _domElement) { + return _domElement.mozAudioChannelType; + } else { + return 'unknown'; + } + }; + + this.whenTimeIncrements = function(callback, context) { + if (_domElement) { + var lastTime, handler; + handler = OT.$.bind(function() { + if (_domElement) { + if (!lastTime || lastTime >= _domElement.currentTime) { + lastTime = _domElement.currentTime; + } else { + _domElement.removeEventListener('timeupdate', handler, false); + callback.call(context, this); + } + } + }, this); + _domElement.addEventListener('timeupdate', handler, false); + } + }; + + this.destroy = function() { + this.unbindStream(); + + if (_domElement) { + // Unbind this first, otherwise it will trigger when the + // video element is removed from the DOM. + _domElement.removeEventListener('pause', _playVideoOnPause); + + OT.$.removeElement(_domElement); + _domElement = null; + } + + return void 0; + }; + + this.onRatioAvailable = function(callback) { + if(ratioAvailable) { + callback(); + } else { + ratioAvailableListeners.push(callback); + } + }; + }; + +/// Private Helper functions + + // A mixin to create the orientation API implementation on +self+ + // +getDomElementCallback+ is a function that the mixin will call when it wants to + // get the native Dom element for +self+. + // + // +initialOrientation+ sets the initial orientation (shockingly), it's currently unused + // so the initial value is actually undefined. + // + var canBeOrientatedMixin = function canBeOrientatedMixin (self, + getDomElementCallback, + orientationChangedHandler, + initialOrientation) { + var _orientation = initialOrientation; + + OT.$.defineProperties(self, { + isRotated: { + get: function() { + return this.orientation() && + (this.orientation().videoOrientation === 270 || + this.orientation().videoOrientation === 90); + } + }, + + orientation: { + get: function() { return _orientation; }, + set: function(orientation) { + _orientation = orientation; + + var transform = VideoOrientationTransforms[orientation.videoOrientation] || + VideoOrientationTransforms.ROTATED_NORMAL; + + switch(OT.$.env.name) { + case 'Chrome': + case 'Safari': + getDomElementCallback().style.webkitTransform = transform; + break; + + case 'IE': + if (OT.$.env.version >= 9) { + getDomElementCallback().style.msTransform = transform; + } + else { + // So this basically defines matrix that represents a rotation + // of a single vector in a 2d basis. + // + // R = [cos(Theta) -sin(Theta)] + // [sin(Theta) cos(Theta)] + // + // Where Theta is the number of radians to rotate by + // + // Then to rotate the vector v: + // v' = Rv + // + // We then use IE8 Matrix filter property, which takes + // a 2x2 rotation matrix, to rotate our DOM element. + // + var radians = orientation.videoOrientation * DEGREE_TO_RADIANS, + element = getDomElementCallback(), + costheta = Math.cos(radians), + sintheta = Math.sin(radians); + + // element.filters.item(0).M11 = costheta; + // element.filters.item(0).M12 = -sintheta; + // element.filters.item(0).M21 = sintheta; + // element.filters.item(0).M22 = costheta; + + element.style.filter = 'progid:DXImageTransform.Microsoft.Matrix(' + + 'M11='+costheta+',' + + 'M12='+(-sintheta)+',' + + 'M21='+sintheta+',' + + 'M22='+costheta+',SizingMethod=\'auto expand\')'; + } + + + break; + + default: + // The standard version, just Firefox, Opera, and IE > 9 + getDomElementCallback().style.transform = transform; + } + + orientationChangedHandler(_orientation); + + } + }, + + // see https://wiki.mozilla.org/WebAPI/AudioChannels + // The audioChannelType is currently only available in Firefox. This property returns + // "unknown" in other browser. The related HTML tag attribute is "mozaudiochannel" + audioChannelType: { + get: function() { + if ('mozAudioChannelType' in this.domElement) { + return this.domElement.mozAudioChannelType; + } else { + return 'unknown'; + } + }, + set: function(type) { + if ('mozAudioChannelType' in this.domElement) { + this.domElement.mozAudioChannelType = type; + } + } + } + }); + }; + + function createNativeVideoElement(fallbackText, attributes) { + var videoElement = document.createElement('video'); + videoElement.setAttribute('autoplay', ''); + videoElement.innerHTML = fallbackText; + + if (attributes) { + if (attributes.muted === true) { + delete attributes.muted; + videoElement.muted = 'true'; + } + + for (var key in attributes) { + if(!attributes.hasOwnProperty(key)) { + continue; + } + videoElement.setAttribute(key, attributes[key]); + } + } + + return videoElement; + } + + + // See http://www.w3.org/TR/2010/WD-html5-20101019/video.html#error-codes + var _videoErrorCodes = {}; + + // Checking for window.MediaError for IE compatibility, just so we don't throw + // exceptions when the script is included + if (window.MediaError) { + _videoErrorCodes[window.MediaError.MEDIA_ERR_ABORTED] = 'The fetching process for the media ' + + 'resource was aborted by the user agent at the user\'s request.'; + _videoErrorCodes[window.MediaError.MEDIA_ERR_NETWORK] = 'A network error of some description ' + + 'caused the user agent to stop fetching the media resource, after the resource was ' + + 'established to be usable.'; + _videoErrorCodes[window.MediaError.MEDIA_ERR_DECODE] = 'An error of some description ' + + 'occurred while decoding the media resource, after the resource was established to be ' + + ' usable.'; + _videoErrorCodes[window.MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED] = 'The media resource ' + + 'indicated by the src attribute was not suitable.'; + } + + function videoElementErrorCodeToStr(errorCode) { + return _videoErrorCodes[parseInt(errorCode, 10)] || 'An unknown error occurred.'; + } + + function bindStreamToNativeVideoElement(videoElement, webRtcStream, completion) { + var timeout, + minVideoTracksForTimeUpdate = OT.$.env.name === 'Chrome' ? 1 : 0, + loadedEvent = webRtcStream.getVideoTracks().length > minVideoTracksForTimeUpdate ? + 'timeupdate' : 'loadedmetadata'; + + var cleanup = function cleanup () { + clearTimeout(timeout); + videoElement.removeEventListener(loadedEvent, onLoad, false); + videoElement.removeEventListener('error', onError, false); + webRtcStream.onended = null; + }, + + onLoad = function onLoad () { + cleanup(); + completion(null); + }, + + onError = function onError (event) { + cleanup(); + unbindNativeStream(videoElement); + completion('There was an unexpected problem with the Video Stream: ' + + videoElementErrorCodeToStr(event.target.error.code)); + }, + + onStoppedLoading = function onStoppedLoading () { + // The stream ended before we fully bound it. Maybe the other end called + // stop on it or something else went wrong. + cleanup(); + unbindNativeStream(videoElement); + completion('Stream ended while trying to bind it to a video element.'); + }; + + videoElement.addEventListener(loadedEvent, onLoad, false); + videoElement.addEventListener('error', onError, false); + webRtcStream.onended = onStoppedLoading; + + // The official spec way is 'srcObject', we are slowly converging there. + if (videoElement.srcObject !== void 0) { + videoElement.srcObject = webRtcStream; + } else if (videoElement.mozSrcObject !== void 0) { + videoElement.mozSrcObject = webRtcStream; + } else { + videoElement.src = window.URL.createObjectURL(webRtcStream); + } + } + + + function unbindNativeStream(videoElement) { + if (videoElement.srcObject !== void 0) { + videoElement.srcObject = null; + } else if (videoElement.mozSrcObject !== void 0) { + videoElement.mozSrcObject = null; + } else { + window.URL.revokeObjectURL(videoElement.src); + } + } + + +})(window); + +// tb_require('../helpers.js') +// tb_require('./video_element.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ + +!(function() { +/*global OT:true */ + + var defaultAspectRatio = 4.0/3.0, + miniWidth = 128, + miniHeight = 128, + microWidth = 64, + microHeight = 64; + + /** + * Sets the video element size so by preserving the intrinsic aspect ratio of the element content + * but altering the width and height so that the video completely covers the container. + * + * @param {Element} element the container of the video element + * @param {number} containerWidth + * @param {number} containerHeight + * @param {number} intrinsicRatio the aspect ratio of the video media + * @param {boolean} rotated + */ + function fixFitModeCover(element, containerWidth, containerHeight, intrinsicRatio, rotated) { + + var $video = OT.$('.OT_video-element', element); + + if ($video.length > 0) { + + var cssProps = {left: '', top: ''}; + + if (OTPlugin.isInstalled()) { + cssProps.width = '100%'; + cssProps.height = '100%'; + } else { + intrinsicRatio = intrinsicRatio || defaultAspectRatio; + intrinsicRatio = rotated ? 1 / intrinsicRatio : intrinsicRatio; + + var containerRatio = containerWidth / containerHeight; + + var enforcedVideoWidth, + enforcedVideoHeight; + + if (rotated) { + // in case of rotation same code works for both kind of ration + enforcedVideoHeight = containerWidth; + enforcedVideoWidth = enforcedVideoHeight * intrinsicRatio; + + cssProps.width = enforcedVideoWidth + 'px'; + cssProps.height = enforcedVideoHeight + 'px'; + cssProps.top = (enforcedVideoWidth + containerHeight) / 2 + 'px'; + } else { + if (intrinsicRatio < containerRatio) { + // the container is wider than the video -> we will crop the height of the video + enforcedVideoWidth = containerWidth; + enforcedVideoHeight = enforcedVideoWidth / intrinsicRatio; + + cssProps.width = enforcedVideoWidth + 'px'; + cssProps.height = enforcedVideoHeight + 'px'; + cssProps.top = (-enforcedVideoHeight + containerHeight) / 2 + 'px'; + } else { + enforcedVideoHeight = containerHeight; + enforcedVideoWidth = enforcedVideoHeight * intrinsicRatio; + + cssProps.width = enforcedVideoWidth + 'px'; + cssProps.height = enforcedVideoHeight + 'px'; + cssProps.left = (-enforcedVideoWidth + containerWidth) / 2 + 'px'; + } + } + } + + $video.css(cssProps); + } + } + + /** + * Sets the video element size so that the video is entirely visible inside the container. + * + * @param {Element} element the container of the video element + * @param {number} containerWidth + * @param {number} containerHeight + * @param {number} intrinsicRatio the aspect ratio of the video media + * @param {boolean} rotated + */ + function fixFitModeContain(element, containerWidth, containerHeight, intrinsicRatio, rotated) { + + var $video = OT.$('.OT_video-element', element); + + if ($video.length > 0) { + + var cssProps = {left: '', top: ''}; + + + if (OTPlugin.isInstalled()) { + intrinsicRatio = intrinsicRatio || defaultAspectRatio; + + var containerRatio = containerWidth / containerHeight; + + var enforcedVideoWidth, + enforcedVideoHeight; + + if (intrinsicRatio < containerRatio) { + enforcedVideoHeight = containerHeight; + enforcedVideoWidth = containerHeight * intrinsicRatio; + + cssProps.width = enforcedVideoWidth + 'px'; + cssProps.height = enforcedVideoHeight + 'px'; + cssProps.left = (containerWidth - enforcedVideoWidth) / 2 + 'px'; + } else { + enforcedVideoWidth = containerWidth; + enforcedVideoHeight = enforcedVideoWidth / intrinsicRatio; + + cssProps.width = enforcedVideoWidth + 'px'; + cssProps.height = enforcedVideoHeight + 'px'; + cssProps.top = (containerHeight - enforcedVideoHeight) / 2 + 'px'; + } + } else { + if (rotated) { + cssProps.width = containerHeight + 'px'; + cssProps.height = containerWidth + 'px'; + cssProps.top = containerHeight + 'px'; + } else { + cssProps.width = '100%'; + cssProps.height = '100%'; + } + } + + $video.css(cssProps); + } + } + + function fixMini(container, width, height) { + var w = parseInt(width, 10), + h = parseInt(height, 10); + + if(w < microWidth || h < microHeight) { + OT.$.addClass(container, 'OT_micro'); + } else { + OT.$.removeClass(container, 'OT_micro'); + } + if(w < miniWidth || h < miniHeight) { + OT.$.addClass(container, 'OT_mini'); + } else { + OT.$.removeClass(container, 'OT_mini'); + } + } + + var getOrCreateContainer = function getOrCreateContainer(elementOrDomId, insertMode) { + var container, + domId; + + if (elementOrDomId && elementOrDomId.nodeName) { + // It looks like we were given a DOM element. Grab the id or generate + // one if it doesn't have one. + container = elementOrDomId; + if (!container.getAttribute('id') || container.getAttribute('id').length === 0) { + container.setAttribute('id', 'OT_' + OT.$.uuid()); + } + + domId = container.getAttribute('id'); + } else { + // We may have got an id, try and get it's DOM element. + container = OT.$('#' + elementOrDomId).get(0); + domId = elementOrDomId || ('OT_' + OT.$.uuid()); + } + + if (!container) { + container = OT.$.createElement('div', {id: domId}); + container.style.backgroundColor = '#000000'; + document.body.appendChild(container); + } else { + if(!(insertMode == null || insertMode === 'replace')) { + var placeholder = document.createElement('div'); + placeholder.id = ('OT_' + OT.$.uuid()); + if(insertMode === 'append') { + container.appendChild(placeholder); + container = placeholder; + } else if(insertMode === 'before') { + container.parentNode.insertBefore(placeholder, container); + container = placeholder; + } else if(insertMode === 'after') { + container.parentNode.insertBefore(placeholder, container.nextSibling); + container = placeholder; + } + } else { + OT.$.emptyElement(container); + } + } + + return container; + }; + + // Creates the standard container that the Subscriber and Publisher use to hold + // their video element and other chrome. + OT.WidgetView = function(targetElement, properties) { + + var widgetView = {}; + + var container = getOrCreateContainer(targetElement, properties && properties.insertMode), + widgetContainer = document.createElement('div'), + oldContainerStyles = {}, + dimensionsObserver, + videoElement, + videoObserver, + posterContainer, + loadingContainer, + width, + height, + loading = true, + audioOnly = false, + fitMode = 'cover', + fixFitMode = fixFitModeCover; + + OT.$.eventing(widgetView); + + if (properties) { + width = properties.width; + height = properties.height; + + if (width) { + if (typeof(width) === 'number') { + width = width + 'px'; + } + } + + if (height) { + if (typeof(height) === 'number') { + height = height + 'px'; + } + } + + container.style.width = width ? width : '264px'; + container.style.height = height ? height : '198px'; + container.style.overflow = 'hidden'; + fixMini(container, width || '264px', height || '198px'); + + if (properties.mirror === undefined || properties.mirror) { + OT.$.addClass(container, 'OT_mirrored'); + } + + if (properties.fitMode === 'contain') { + fitMode = 'contain'; + fixFitMode = fixFitModeContain; + } else if (properties.fitMode !== 'cover') { + OT.warn('Invalid fit value "' + properties.fitMode + '" passed. ' + + 'Only "contain" and "cover" can be used.'); + } + } + + if (properties.classNames) OT.$.addClass(container, properties.classNames); + + OT.$(container).addClass('OT_loading OT_fit-mode-' + fitMode); + + OT.$.addClass(widgetContainer, 'OT_widget-container'); + widgetContainer.style.width = '100%'; //container.style.width; + widgetContainer.style.height = '100%'; // container.style.height; + container.appendChild(widgetContainer); + + loadingContainer = document.createElement('div'); + OT.$.addClass(loadingContainer, 'OT_video-loading'); + widgetContainer.appendChild(loadingContainer); + + posterContainer = document.createElement('div'); + OT.$.addClass(posterContainer, 'OT_video-poster'); + widgetContainer.appendChild(posterContainer); + + oldContainerStyles.width = container.offsetWidth; + oldContainerStyles.height = container.offsetHeight; + + if (!OTPlugin.isInstalled()) { + // Observe changes to the width and height and update the aspect ratio + dimensionsObserver = OT.$.observeStyleChanges(container, ['width', 'height'], + function(changeSet) { + var width = changeSet.width ? changeSet.width[1] : container.offsetWidth, + height = changeSet.height ? changeSet.height[1] : container.offsetHeight; + + fixMini(container, width, height); + + if (videoElement) { + fixFitMode(widgetContainer, width, height, videoElement.aspectRatio(), + videoElement.isRotated()); + } + }); + + + // @todo observe if the video container or the video element get removed + // if they do we should do some cleanup + videoObserver = OT.$.observeNodeOrChildNodeRemoval(container, function(removedNodes) { + if (!videoElement) return; + + // This assumes a video element being removed is the main video element. This may + // not be the case. + var videoRemoved = OT.$.some(removedNodes, function(node) { + return node === widgetContainer || node.nodeName === 'VIDEO'; + }); + + if (videoRemoved) { + videoElement.destroy(); + videoElement = null; + } + + if (widgetContainer) { + OT.$.removeElement(widgetContainer); + widgetContainer = null; + } + + if (dimensionsObserver) { + dimensionsObserver.disconnect(); + dimensionsObserver = null; + } + + if (videoObserver) { + videoObserver.disconnect(); + videoObserver = null; + } + }); + } + + widgetView.destroy = function() { + if (dimensionsObserver) { + dimensionsObserver.disconnect(); + dimensionsObserver = null; + } + + if (videoObserver) { + videoObserver.disconnect(); + videoObserver = null; + } + + if (videoElement) { + videoElement.destroy(); + videoElement = null; + } + + if (container) { + OT.$.removeElement(container); + container = null; + } + }; + + widgetView.setBackgroundImageURI = function(bgImgURI) { + if (bgImgURI.substr(0, 5) !== 'http:' && bgImgURI.substr(0, 6) !== 'https:') { + if (bgImgURI.substr(0, 22) !== 'data:image/png;base64,') { + bgImgURI = 'data:image/png;base64,' + bgImgURI; + } + } + OT.$.css(posterContainer, 'backgroundImage', 'url(' + bgImgURI + ')'); + OT.$.css(posterContainer, 'backgroundSize', 'contain'); + OT.$.css(posterContainer, 'opacity', '1.0'); + }; + + if (properties && properties.style && properties.style.backgroundImageURI) { + widgetView.setBackgroundImageURI(properties.style.backgroundImageURI); + } + + widgetView.bindVideo = function(webRTCStream, options, completion) { + // remove the old video element if it exists + // @todo this might not be safe, publishers/subscribers use this as well... + if (videoElement) { + videoElement.destroy(); + videoElement = null; + } + + var onError = options && options.error ? options.error : void 0; + delete options.error; + + var video = new OT.VideoElement({attributes: options}, onError); + + // Initialize the audio volume + if (options.audioVolume) video.setAudioVolume(options.audioVolume); + + // makes the incoming audio streams take priority (will impact only FF OS for now) + video.audioChannelType('telephony'); + + video.appendTo(widgetContainer).bindToStream(webRTCStream, function(err) { + if (err) { + video.destroy(); + completion(err); + return; + } + + videoElement = video; + + // clear inline height value we used to init plugin rendering + OT.$.css(video.domElement(), 'height', ''); + + var fixFitModePartial = function() { + fixFitMode(widgetContainer, container.offsetWidth, container.offsetHeight, + video.aspectRatio(), video.isRotated()); + }; + + video.on({ + orientationChanged: fixFitModePartial, + videoDimensionsChanged: function(oldValue, newValue) { + fixFitModePartial(); + widgetView.trigger('videoDimensionsChanged', oldValue, newValue); + }, + mediaStopped: function() { + widgetView.trigger('mediaStopped'); + } + }); + + video.onRatioAvailable(fixFitModePartial); + + completion(null, video); + }); + + OT.$.addClass(video.domElement(), 'OT_video-element'); + + // plugin needs a minimum of height to be rendered and kicked off + // we will reset that once the video is bound to the stream + OT.$.css(video.domElement(), 'height', '1px'); + + return video; + }; + + OT.$.defineProperties(widgetView, { + + video: { + get: function() { + return videoElement; + } + }, + + showPoster: { + get: function() { + return !OT.$.isDisplayNone(posterContainer); + }, + set: function(newValue) { + if(newValue) { + OT.$.show(posterContainer); + } else { + OT.$.hide(posterContainer); + } + } + }, + + poster: { + get: function() { + return OT.$.css(posterContainer, 'backgroundImage'); + }, + set: function(src) { + OT.$.css(posterContainer, 'backgroundImage', 'url(' + src + ')'); + } + }, + + loading: { + get: function() { return loading; }, + set: function(l) { + loading = l; + + if (loading) { + OT.$.addClass(container, 'OT_loading'); + } else { + OT.$.removeClass(container, 'OT_loading'); + } + } + }, + + audioOnly: { + get: function() { return audioOnly; }, + set: function(a) { + audioOnly = a; + + if (audioOnly) { + OT.$.addClass(container, 'OT_audio-only'); + } else { + OT.$.removeClass(container, 'OT_audio-only'); + } + } + }, + + domId: { + get: function() { return container.getAttribute('id'); } + } + + }); + + widgetView.domElement = container; + + widgetView.addError = function(errorMsg, helpMsg, classNames) { + container.innerHTML = '

' + errorMsg + + (helpMsg ? ' ' + helpMsg + '' : '') + + '

'; + OT.$.addClass(container, classNames || 'OT_subscriber_error'); + if(container.querySelector('p').offsetHeight > container.offsetHeight) { + container.querySelector('span').style.display = 'none'; + } + }; + + return widgetView; + }; + +})(window); + +// tb_require('../helpers.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ + +if (!OT.properties) { + throw new Error('OT.properties does not exist, please ensure that you include a valid ' + + 'properties file.'); +} + +OT.useSSL = function () { + return OT.properties.supportSSL && (window.location.protocol.indexOf('https') >= 0 || + window.location.protocol.indexOf('chrome-extension') >= 0); +}; + +// Consumes and overwrites OT.properties. Makes it better and stronger! +OT.properties = function(properties) { + var props = OT.$.clone(properties); + + props.debug = properties.debug === 'true' || properties.debug === true; + props.supportSSL = properties.supportSSL === 'true' || properties.supportSSL === true; + + if (window.OTProperties) { + // Allow window.OTProperties to override cdnURL, configURL, assetURL and cssURL + if (window.OTProperties.cdnURL) props.cdnURL = window.OTProperties.cdnURL; + if (window.OTProperties.cdnURLSSL) props.cdnURLSSL = window.OTProperties.cdnURLSSL; + if (window.OTProperties.configURL) props.configURL = window.OTProperties.configURL; + if (window.OTProperties.assetURL) props.assetURL = window.OTProperties.assetURL; + if (window.OTProperties.cssURL) props.cssURL = window.OTProperties.cssURL; + } + + if (!props.assetURL) { + if (OT.useSSL()) { + props.assetURL = props.cdnURLSSL + '/webrtc/' + props.version; + } else { + props.assetURL = props.cdnURL + '/webrtc/' + props.version; + } + } + + var isIE89 = OT.$.env.name === 'IE' && OT.$.env.version <= 9; + if (!(isIE89 && window.location.protocol.indexOf('https') < 0)) { + props.apiURL = props.apiURLSSL; + props.loggingURL = props.loggingURLSSL; + } + + if (!props.configURL) props.configURL = props.assetURL + '/js/dynamic_config.min.js'; + if (!props.cssURL) props.cssURL = props.assetURL + '/css/TB.min.css'; + + return props; +}(OT.properties); + +// tb_require('../helpers.js') + +!(function() { + /* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ + /* global OT */ + + var currentGuidStorage, + currentGuid; + + var isInvalidStorage = function isInvalidStorage (storageInterface) { + return !(OT.$.isFunction(storageInterface.get) && OT.$.isFunction(storageInterface.set)); + }; + + var getClientGuid = function getClientGuid (completion) { + if (currentGuid) { + completion(null, currentGuid); + return; + } + + // It's the first time that getClientGuid has been called + // in this page lifetime. Attempt to load any existing Guid + // from the storage + currentGuidStorage.get(completion); + }; + + /* + * Sets the methods for storing and retrieving client GUIDs persistently + * across sessions. By default, OpenTok.js attempts to use browser cookies to + * store GUIDs. + *

+ * Pass in an object that has a get() method and + * a set() method. + *

+ * The get() method must take one parameter: the callback + * method to invoke. The callback method is passed two parameters — + * the first parameter is an error object or null if the call is successful; + * and the second parameter is the GUID (a string) if successful. + *

+ * The set() method must include two parameters: the GUID to set + * (a string) and the callback method to invoke. The callback method is + * passed an error object on error, or it is passed no parameter if the call is + * successful. + *

+ * Here is an example: + *

+ *

+  * var ComplexStorage = function() {
+  *   this.set = function(guid, completion) {
+  *     AwesomeBackendService.set(guid, function(response) {
+  *       completion(response.error || null);
+  *     });
+  *   };
+  *   this.get = function(completion) {
+  *     AwesomeBackendService.get(function(response, guid) {
+  *       completion(response.error || null, guid);
+  *     });
+  *   };
+  * };
+  *
+  * OT.overrideGuidStorage(new ComplexStorage());
+  * 
+ */ + OT.overrideGuidStorage = function (storageInterface) { + if (isInvalidStorage(storageInterface)) { + throw new Error('The storageInterface argument does not seem to be valid, ' + + 'it must implement get and set methods'); + } + + if (currentGuidStorage === storageInterface) { + return; + } + + currentGuidStorage = storageInterface; + + // If a client Guid has already been assigned to this client then + // let the new storage know about it so that it's in sync. + if (currentGuid) { + currentGuidStorage.set(currentGuid, function(error) { + if (error) { + OT.error('Failed to send initial Guid value (' + currentGuid + + ') to the newly assigned Guid Storage. The error was: ' + error); + // @todo error + } + }); + } + }; + + if (!OT._) OT._ = {}; + OT._.getClientGuid = function (completion) { + getClientGuid(function(error, guid) { + if (error) { + completion(error); + return; + } + + if (!guid) { + // Nothing came back, this client is entirely new. + // generate a new Guid and persist it + guid = OT.$.uuid(); + currentGuidStorage.set(guid, function(error) { + if (error) { + completion(error); + return; + } + + currentGuid = guid; + }); + } + else if (!currentGuid) { + currentGuid = guid; + } + + completion(null, currentGuid); + }); + }; + + + // Implement our default storage mechanism, which sets/gets a cookie + // called 'opentok_client_id' + OT.overrideGuidStorage({ + get: function(completion) { + completion(null, OT.$.getCookie('opentok_client_id')); + }, + + set: function(guid, completion) { + OT.$.setCookie('opentok_client_id', guid); + completion(null); + } + }); + +})(window); + +// tb_require('../helpers.js') +// tb_require('./web_rtc.js') + +// Web OT Helpers +!(function() { + /* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ + /* global OT */ + + var nativeGetUserMedia, + vendorToW3CErrors, + gumNamesToMessages, + mapVendorErrorName, + parseErrorEvent, + areInvalidConstraints; + + // Handy cross-browser getUserMedia shim. Inspired by some code from Adam Barth + nativeGetUserMedia = (function() { + if (navigator.getUserMedia) { + return OT.$.bind(navigator.getUserMedia, navigator); + } else if (navigator.mozGetUserMedia) { + return OT.$.bind(navigator.mozGetUserMedia, navigator); + } else if (navigator.webkitGetUserMedia) { + return OT.$.bind(navigator.webkitGetUserMedia, navigator); + } else if (OTPlugin.isInstalled()) { + return OT.$.bind(OTPlugin.getUserMedia, OTPlugin); + } + })(); + + // Mozilla error strings and the equivalent W3C names. NOT_SUPPORTED_ERROR does not + // exist in the spec right now, so we'll include Mozilla's error description. + // Chrome TrackStartError is triggered when the camera is already used by another app (Windows) + vendorToW3CErrors = { + PERMISSION_DENIED: 'PermissionDeniedError', + NOT_SUPPORTED_ERROR: 'NotSupportedError', + MANDATORY_UNSATISFIED_ERROR: ' ConstraintNotSatisfiedError', + NO_DEVICES_FOUND: 'NoDevicesFoundError', + HARDWARE_UNAVAILABLE: 'HardwareUnavailableError', + TrackStartError: 'HardwareUnavailableError' + }; + + gumNamesToMessages = { + PermissionDeniedError: 'End-user denied permission to hardware devices', + PermissionDismissedError: 'End-user dismissed permission to hardware devices', + NotSupportedError: 'A constraint specified is not supported by the browser.', + ConstraintNotSatisfiedError: 'It\'s not possible to satisfy one or more constraints ' + + 'passed into the getUserMedia function', + OverconstrainedError: 'Due to changes in the environment, one or more mandatory ' + + 'constraints can no longer be satisfied.', + NoDevicesFoundError: 'No voice or video input devices are available on this machine.', + HardwareUnavailableError: 'The selected voice or video devices are unavailable. Verify ' + + 'that the chosen devices are not in use by another application.' + }; + + // Map vendor error strings to names and messages if possible + mapVendorErrorName = function mapVendorErrorName(vendorErrorName, vendorErrors) { + var errorName, errorMessage; + + if(vendorErrors.hasOwnProperty(vendorErrorName)) { + errorName = vendorErrors[vendorErrorName]; + } else { + // This doesn't map to a known error from the Media Capture spec, it's + // probably a custom vendor error message. + errorName = vendorErrorName; + } + + if(gumNamesToMessages.hasOwnProperty(errorName)) { + errorMessage = gumNamesToMessages[errorName]; + } else { + errorMessage = 'Unknown Error while getting user media'; + } + + return { + name: errorName, + message: errorMessage + }; + }; + + // Parse and normalise a getUserMedia error event from Chrome or Mozilla + // @ref http://dev.w3.org/2011/webrtc/editor/getusermedia.html#idl-def-NavigatorUserMediaError + parseErrorEvent = function parseErrorObject(event) { + var error; + + if (OT.$.isObject(event) && event.name) { + error = mapVendorErrorName(event.name, vendorToW3CErrors); + error.constraintName = event.constraintName; + } else if (typeof event === 'string') { + error = mapVendorErrorName(event, vendorToW3CErrors); + } else { + error = { + message: 'Unknown Error type while getting media' + }; + } + + return error; + }; + + // Validates a Hash of getUserMedia constraints. Currently we only + // check to see if there is at least one non-false constraint. + areInvalidConstraints = function(constraints) { + if (!constraints || !OT.$.isObject(constraints)) return true; + + for (var key in constraints) { + if(!constraints.hasOwnProperty(key)) { + continue; + } + if (constraints[key]) return false; + } + + return true; + }; + + + // A wrapper for the builtin navigator.getUserMedia. In addition to the usual + // getUserMedia behaviour, this helper method also accepts a accessDialogOpened + // and accessDialogClosed callback. + // + // @memberof OT.$ + // @private + // + // @param {Object} constraints + // A dictionary of constraints to pass to getUserMedia. See + // http://dev.w3.org/2011/webrtc/editor/getusermedia.html#idl-def-MediaStreamConstraints + // in the Media Capture and Streams spec for more info. + // + // @param {function} success + // Called when getUserMedia completes successfully. The callback will be passed a WebRTC + // Stream object. + // + // @param {function} failure + // Called when getUserMedia fails to access a user stream. It will be passed an object + // with a code property representing the error that occurred. + // + // @param {function} accessDialogOpened + // Called when the access allow/deny dialog is opened. + // + // @param {function} accessDialogClosed + // Called when the access allow/deny dialog is closed. + // + // @param {function} accessDenied + // Called when access is denied to the camera/mic. This will be either because + // the user has clicked deny or because a particular origin is permanently denied. + // + OT.$.getUserMedia = function(constraints, success, failure, accessDialogOpened, + accessDialogClosed, accessDenied, customGetUserMedia) { + + var getUserMedia = nativeGetUserMedia; + + if(OT.$.isFunction(customGetUserMedia)) { + getUserMedia = customGetUserMedia; + } + + // All constraints are false, we don't allow this. This may be valid later + // depending on how/if we integrate data channels. + if (areInvalidConstraints(constraints)) { + OT.error('Couldn\'t get UserMedia: All constraints were false'); + // Using a ugly dummy-code for now. + failure.call(null, { + name: 'NO_VALID_CONSTRAINTS', + message: 'Video and Audio was disabled, you need to enabled at least one' + }); + + return; + } + + var triggerOpenedTimer = null, + displayedPermissionDialog = false, + + finaliseAccessDialog = function() { + if (triggerOpenedTimer) { + clearTimeout(triggerOpenedTimer); + } + + if (displayedPermissionDialog && accessDialogClosed) accessDialogClosed(); + }, + + triggerOpened = function() { + triggerOpenedTimer = null; + displayedPermissionDialog = true; + + if (accessDialogOpened) accessDialogOpened(); + }, + + onStream = function(stream) { + finaliseAccessDialog(); + success.call(null, stream); + }, + + onError = function(event) { + finaliseAccessDialog(); + var error = parseErrorEvent(event); + + // The error name 'PERMISSION_DENIED' is from an earlier version of the spec + if (error.name === 'PermissionDeniedError' || error.name === 'PermissionDismissedError') { + accessDenied.call(null, error); + } else { + failure.call(null, error); + } + }; + + try { + getUserMedia(constraints, onStream, onError); + } catch (e) { + OT.error('Couldn\'t get UserMedia: ' + e.toString()); + onError(); + return; + } + + // The 'remember me' functionality of WebRTC only functions over HTTPS, if + // we aren't on HTTPS then we should definitely be displaying the access + // dialog. + // + // If we are on HTTPS, we'll wait 500ms to see if we get a stream + // immediately. If we do then the user had clicked 'remember me'. Otherwise + // we assume that the accessAllowed dialog is visible. + // + // @todo benchmark and see if 500ms is a reasonable number. It seems like + // we should know a lot quicker. + // + if (location.protocol.indexOf('https') === -1) { + // Execute after, this gives the client a chance to bind to the + // accessDialogOpened event. + triggerOpenedTimer = setTimeout(triggerOpened, 100); + + } else { + // wait a second and then trigger accessDialogOpened + triggerOpenedTimer = setTimeout(triggerOpened, 500); + } }; })(); + +// tb_require('../helpers.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ + !(function() { + var adjustModal = function(callback) { + return function setFullHeightDocument(window, document) { + // required in IE8 + document.querySelector('html').style.height = document.body.style.height = '100%'; + callback(window, document); + }; + }; + + var addCss = function(document, url, callback) { + var head = document.head || document.getElementsByTagName('head')[0]; + var cssTag = OT.$.createElement('link', { + type: 'text/css', + media: 'screen', + rel: 'stylesheet', + href: url + }); + head.appendChild(cssTag); + OT.$.on(cssTag, 'error', function(error) { + OT.error('Could not load CSS for dialog', url, error && error.message || error); + }); + OT.$.on(cssTag, 'load', callback); + }; + + var addDialogCSS = function(document, urls, callback) { + var allURLs = [ + '//fonts.googleapis.com/css?family=Didact+Gothic', + OT.properties.cssURL + ].concat(urls); + var remainingStylesheets = allURLs.length; + OT.$.forEach(allURLs, function(stylesheetUrl) { + addCss(document, stylesheetUrl, function() { + if(--remainingStylesheets <= 0) { + callback(); + } + }); + }); + + }; + + var templateElement = function(classes, children, tagName) { + var el = OT.$.createElement(tagName || 'div', { 'class': classes }, children, this); + el.on = OT.$.bind(OT.$.on, OT.$, el); + el.off = OT.$.bind(OT.$.off, OT.$, el); + return el; + }; + + var checkBoxElement = function (classes, nameAndId, onChange) { + var checkbox = templateElement.call(this, '', null, 'input'); + checkbox = OT.$(checkbox).on('change', onChange); + + if (OT.$.env.name === 'IE' && OT.$.env.version <= 8) { + // Fix for IE8 not triggering the change event + checkbox.on('click', function() { + checkbox.first.blur(); + checkbox.first.focus(); + }); + } + + checkbox.attr({ + name: nameAndId, + id: nameAndId, + type: 'checkbox' + }); + + return checkbox.first; + }; + + var linkElement = function(children, href, classes) { + var link = templateElement.call(this, classes || '', children, 'a'); + link.setAttribute('href', href); + return link; + }; + + OT.Dialogs = {}; + + OT.Dialogs.Plugin = {}; + + OT.Dialogs.Plugin.promptToInstall = function() { + var modal = new OT.$.Modal(adjustModal(function(window, document) { + + var el = OT.$.bind(templateElement, document), + btn = function(children, size) { + var classes = 'OT_dialog-button ' + + (size ? 'OT_dialog-button-' + size : 'OT_dialog-button-large'), + b = el(classes, children); + + b.enable = function() { + OT.$.removeClass(this, 'OT_dialog-button-disabled'); + return this; + }; + + b.disable = function() { + OT.$.addClass(this, 'OT_dialog-button-disabled'); + return this; + }; + + return b; + }, + downloadButton = btn('Download plugin'), + cancelButton = btn('cancel', 'small'), + refreshButton = btn('Refresh browser'), + acceptEULA, + checkbox, + close, + root; + + OT.$.addClass(cancelButton, 'OT_dialog-no-natural-margin OT_dialog-button-block'); + OT.$.addClass(refreshButton, 'OT_dialog-no-natural-margin'); + + function onDownload() { + modal.trigger('download'); + setTimeout(function() { + root.querySelector('.OT_dialog-messages-main').innerHTML = + 'Plugin installation successful'; + var sections = root.querySelectorAll('.OT_dialog-section'); + OT.$.addClass(sections[0], 'OT_dialog-hidden'); + OT.$.removeClass(sections[1], 'OT_dialog-hidden'); + }, 3000); + } + + function onRefresh() { + modal.trigger('refresh'); + } + + function onToggleEULA() { + if (checkbox.checked) { + enableButtons(); + } + else { + disableButtons(); + } + } + + function enableButtons() { + downloadButton.enable(); + downloadButton.on('click', onDownload); + + refreshButton.enable(); + refreshButton.on('click', onRefresh); + } + + function disableButtons() { + downloadButton.disable(); + downloadButton.off('click', onDownload); + + refreshButton.disable(); + refreshButton.off('click', onRefresh); + } + + downloadButton.disable(); + refreshButton.disable(); + + cancelButton.on('click', function() { + modal.trigger('cancelButtonClicked'); + modal.close(); + }); + + close = el('OT_closeButton', '×') + .on('click', function() { + modal.trigger('closeButtonClicked'); + modal.close(); + }).first; + + var protocol = (window.location.protocol.indexOf('https') >= 0 ? 'https' : 'http'); + acceptEULA = linkElement.call(document, + 'end-user license agreement', + protocol + '://tokbox.com/support/ie-eula'); + + checkbox = checkBoxElement.call(document, null, 'acceptEULA', onToggleEULA); + + root = el('OT_dialog-centering', [ + el('OT_dialog-centering-child', [ + el('OT_root OT_dialog OT_dialog-plugin-prompt', [ + close, + el('OT_dialog-messages', [ + el('OT_dialog-messages-main', 'This app requires real-time communication') + ]), + el('OT_dialog-section', [ + el('OT_dialog-single-button-with-title', [ + el('OT_dialog-button-title', [ + checkbox, + (function() { + var x = el('', 'accept', 'label'); + x.setAttribute('for', checkbox.id); + x.style.margin = '0 5px'; + return x; + })(), + acceptEULA + ]), + el('OT_dialog-actions-card', [ + downloadButton, + cancelButton + ]) + ]) + ]), + el('OT_dialog-section OT_dialog-hidden', [ + el('OT_dialog-button-title', [ + 'You can now enjoy webRTC enabled video via Internet Explorer.' + ]), + refreshButton + ]) + ]) + ]) + ]); + + addDialogCSS(document, [], function() { + document.body.appendChild(root); + }); + + })); + return modal; + }; + + OT.Dialogs.Plugin.promptToReinstall = function() { + var modal = new OT.$.Modal(adjustModal(function(window, document) { + + var el = OT.$.bind(templateElement, document), + close, + okayButton, + root; + + close = el('OT_closeButton', '×') + .on('click', function() { + modal.trigger('closeButtonClicked'); + modal.close(); + }).first; + + okayButton = + el('OT_dialog-button OT_dialog-button-large OT_dialog-no-natural-margin', 'Okay') + .on('click', function() { + modal.trigger('okay'); + }).first; + + root = el('OT_dialog-centering', [ + el('OT_dialog-centering-child', [ + el('OT_ROOT OT_dialog OT_dialog-plugin-reinstall', [ + close, + el('OT_dialog-messages', [ + el('OT_dialog-messages-main', 'Reinstall Opentok Plugin'), + el('OT_dialog-messages-minor', 'Uh oh! Try reinstalling the OpenTok plugin again ' + + 'to enable real-time video communication for Internet Explorer.') + ]), + el('OT_dialog-section', [ + el('OT_dialog-single-button', okayButton) + ]) + ]) + ]) + ]); + + addDialogCSS(document, [], function() { + document.body.appendChild(root); + }); + + })); + + return modal; + }; + + OT.Dialogs.Plugin.updateInProgress = function() { + + var progressBar, + progressText, + progressValue = 0; + + var modal = new OT.$.Modal(adjustModal(function(window, document) { + + var el = OT.$.bind(templateElement, document), + root; + + progressText = el('OT_dialog-plugin-upgrade-percentage', '0%', 'strong'); + + progressBar = el('OT_dialog-progress-bar-fill'); + + root = el('OT_dialog-centering', [ + el('OT_dialog-centering-child', [ + el('OT_ROOT OT_dialog OT_dialog-plugin-upgrading', [ + el('OT_dialog-messages', [ + el('OT_dialog-messages-main', [ + 'One moment please... ', + progressText + ]), + el('OT_dialog-progress-bar', progressBar), + el('OT_dialog-messages-minor OT_dialog-no-natural-margin', + 'Please wait while the OpenTok plugin is updated') + ]) + ]) + ]) + ]); + + addDialogCSS(document, [], function() { + document.body.appendChild(root); + if(progressValue != null) { + modal.setUpdateProgress(progressValue); + } + }); + })); + + modal.setUpdateProgress = function(newProgress) { + if(progressBar && progressText) { + if(newProgress > 99) { + OT.$.css(progressBar, 'width', ''); + progressText.innerHTML = '100%'; + } else if(newProgress < 1) { + OT.$.css(progressBar, 'width', '0%'); + progressText.innerHTML = '0%'; + } else { + OT.$.css(progressBar, 'width', newProgress + '%'); + progressText.innerHTML = newProgress + '%'; + } + } else { + progressValue = newProgress; + } + }; + + return modal; + }; + + OT.Dialogs.Plugin.updateComplete = function(error) { + var modal = new OT.$.Modal(adjustModal(function(window, document) { + var el = OT.$.bind(templateElement, document), + reloadButton, + root; + + reloadButton = + el('OT_dialog-button OT_dialog-button-large OT_dialog-no-natural-margin', 'Reload') + .on('click', function() { + modal.trigger('reload'); + }).first; + + var msgs; + + if(error) { + msgs = ['Update Failed.', error + '' || 'NO ERROR']; + } else { + msgs = ['Update Complete.', + 'The OpenTok plugin has been succesfully updated. ' + + 'Please reload your browser.']; + } + + root = el('OT_dialog-centering', [ + el('OT_dialog-centering-child', [ + el('OT_root OT_dialog OT_dialog-plugin-upgraded', [ + el('OT_dialog-messages', [ + el('OT_dialog-messages-main', msgs[0]), + el('OT_dialog-messages-minor', msgs[1]) + ]), + el('OT_dialog-single-button', reloadButton) + ]) + ]) + ]); + + addDialogCSS(document, [], function() { + document.body.appendChild(root); + }); + + })); + + return modal; + + }; + + +})(); + +// tb_require('../helpers.js') +// tb_require('./web_rtc.js') + +// Web OT Helpers +!(function(window) { + + /* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ + /* global OT */ + + /// + // Device Helpers + // + // Support functions to enumerating and guerying device info + // + + var chromeToW3CDeviceKinds = { + audio: 'audioInput', + video: 'videoInput' + }; + + + OT.$.shouldAskForDevices = function(callback) { + var MST = window.MediaStreamTrack; + + if(MST != null && OT.$.isFunction(MST.getSources)) { + window.MediaStreamTrack.getSources(function(sources) { + var hasAudio = sources.some(function(src) { + return src.kind === 'audio'; + }); + + var hasVideo = sources.some(function(src) { + return src.kind === 'video'; + }); + + callback.call(null, { video: hasVideo, audio: hasAudio }); + }); + + } else { + // This environment can't enumerate devices anyway, so we'll memorise this result. + OT.$.shouldAskForDevices = function(callback) { + setTimeout(OT.$.bind(callback, null, { video: true, audio: true })); + }; + + OT.$.shouldAskForDevices(callback); + } + }; + + + OT.$.getMediaDevices = function(callback) { + if(OT.$.hasCapabilities('getMediaDevices')) { + window.MediaStreamTrack.getSources(function(sources) { + var filteredSources = OT.$.filter(sources, function(source) { + return chromeToW3CDeviceKinds[source.kind] != null; + }); + callback(void 0, OT.$.map(filteredSources, function(source) { + return { + deviceId: source.id, + label: source.label, + kind: chromeToW3CDeviceKinds[source.kind] + }; + })); + }); + } else { + callback(new Error('This browser does not support getMediaDevices APIs')); + } + }; + +})(window); +// tb_require('../helpers.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* exported loadCSS */ + +var loadCSS = function loadCSS(cssURL) { + var style = document.createElement('link'); + style.type = 'text/css'; + style.media = 'screen'; + style.rel = 'stylesheet'; + style.href = cssURL; + var head = document.head || document.getElementsByTagName('head')[0]; + head.appendChild(style); +}; + +// tb_require('../helpers.js') +// tb_require('./properties.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ + +//-------------------------------------- +// JS Dynamic Config +//-------------------------------------- + +OT.Config = (function() { + var _loaded = false, + _global = {}, + _partners = {}, + _script, + _head = document.head || document.getElementsByTagName('head')[0], + _loadTimer, + + _clearTimeout = function() { + if (_loadTimer) { + clearTimeout(_loadTimer); + _loadTimer = null; + } + }, + + _cleanup = function() { + _clearTimeout(); + + if (_script) { + _script.onload = _script.onreadystatechange = null; + + if ( _head && _script.parentNode ) { + _head.removeChild( _script ); + } + + _script = undefined; + } + }, + + _onLoad = function() { + // Only IE and Opera actually support readyState on Script elements. + if (_script.readyState && !/loaded|complete/.test( _script.readyState )) { + // Yeah, we're not ready yet... + return; + } + + _clearTimeout(); + + if (!_loaded) { + // Our config script is loaded but there is not config (as + // replaceWith wasn't called). Something went wrong. Possibly + // the file we loaded wasn't actually a valid config file. + _this._onLoadTimeout(); + } + }, + + _onLoadError = function(/* event */) { + _cleanup(); + + OT.warn('TB DynamicConfig failed to load due to an error'); + this.trigger('dynamicConfigLoadFailed'); + }, + + _getModule = function(moduleName, apiKey) { + if (apiKey && _partners[apiKey] && _partners[apiKey][moduleName]) { + return _partners[apiKey][moduleName]; + } + + return _global[moduleName]; + }, + + _this; + + _this = { + // In ms + loadTimeout: 4000, + + _onLoadTimeout: function() { + _cleanup(); + + OT.warn('TB DynamicConfig failed to load in ' + _this.loadTimeout + ' ms'); + this.trigger('dynamicConfigLoadFailed'); + }, + + load: function(configUrl) { + if (!configUrl) throw new Error('You must pass a valid configUrl to Config.load'); + + _loaded = false; + + setTimeout(function() { + _script = document.createElement( 'script' ); + _script.async = 'async'; + _script.src = configUrl; + _script.onload = _script.onreadystatechange = OT.$.bind(_onLoad, _this); + _script.onerror = OT.$.bind(_onLoadError, _this); + _head.appendChild(_script); + },1); + + _loadTimer = setTimeout(function() { + _this._onLoadTimeout(); + }, this.loadTimeout); + }, + + + isLoaded: function() { + return _loaded; + }, + + reset: function() { + this.off(); + _cleanup(); + _loaded = false; + _global = {}; + _partners = {}; + }, + + // This is public so that the dynamic config file can load itself. + // Using it for other purposes is discouraged, but not forbidden. + replaceWith: function(config) { + _cleanup(); + + if (!config) config = {}; + + _global = config.global || {}; + _partners = config.partners || {}; + + if (!_loaded) _loaded = true; + this.trigger('dynamicConfigChanged'); + }, + + // @example Get the value that indicates whether exceptionLogging is enabled + // OT.Config.get('exceptionLogging', 'enabled'); + // + // @example Get a key for a specific partner, fallback to the default if there is + // no key for that partner + // OT.Config.get('exceptionLogging', 'enabled', 'apiKey'); + // + get: function(moduleName, key, apiKey) { + var module = _getModule(moduleName, apiKey); + return module ? module[key] : null; + } + }; + + OT.$.eventing(_this); + + return _this; +})(); + +// tb_require('../helpers.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OTPlugin, OT */ + +/// +// Capabilities +// +// Support functions to query browser/client Media capabilities. +// + + +// Indicates whether this client supports the getUserMedia +// API. +// +OT.$.registerCapability('getUserMedia', function() { + if (OT.$.env === 'Node') return false; + return !!(navigator.webkitGetUserMedia || + navigator.mozGetUserMedia || + OTPlugin.isInstalled()); +}); + + + +// TODO Remove all PeerConnection stuff, that belongs to the messaging layer not the Media layer. +// Indicates whether this client supports the PeerConnection +// API. +// +// Chrome Issues: +// * The explicit prototype.addStream check is because webkitRTCPeerConnection was +// partially implemented, but not functional, in Chrome 22. +// +// Firefox Issues: +// * No real support before Firefox 19 +// * Firefox 19 has issues with generating Offers. +// * Firefox 20 doesn't interoperate with Chrome. +// +OT.$.registerCapability('PeerConnection', function() { + if (OT.$.env === 'Node') { + return false; + } + else if (typeof(window.webkitRTCPeerConnection) === 'function' && + !!window.webkitRTCPeerConnection.prototype.addStream) { + return true; + } else if (typeof(window.mozRTCPeerConnection) === 'function' && OT.$.env.version > 20.0) { + return true; + } else { + return OTPlugin.isInstalled(); + } +}); + + + +// Indicates whether this client supports WebRTC +// +// This is defined as: getUserMedia + PeerConnection + exceeds min browser version +// +OT.$.registerCapability('webrtc', function() { + if (OT.properties) { + var minimumVersions = OT.properties.minimumVersion || {}, + minimumVersion = minimumVersions[OT.$.env.name.toLowerCase()]; + + if(minimumVersion && OT.$.env.versionGreaterThan(minimumVersion)) { + OT.debug('Support for', OT.$.env.name, 'is disabled because we require', + minimumVersion, 'but this is', OT.$.env.version); + return false; + } + } + + if (OT.$.env === 'Node') { + // Node works, even though it doesn't have getUserMedia + return true; + } + + return OT.$.hasCapabilities('getUserMedia', 'PeerConnection'); +}); + + +// TODO Remove all transport stuff, that belongs to the messaging layer not the Media layer. +// Indicates if the browser supports bundle +// +// Broadly: +// * Firefox doesn't support bundle +// * Chrome support bundle +// * OT Plugin supports bundle +// * We assume NodeJs supports bundle (e.g. 'you're on your own' mode) +// +OT.$.registerCapability('bundle', function() { + return OT.$.hasCapabilities('webrtc') && + (OT.$.env.name === 'Chrome' || + OT.$.env.name === 'Node' || + OTPlugin.isInstalled()); +}); + +// Indicates if the browser supports RTCP Mux +// +// Broadly: +// * Older versions of Firefox (<= 25) don't support RTCP Mux +// * Older versions of Firefox (>= 26) support RTCP Mux (not tested yet) +// * Chrome support RTCP Mux +// * OT Plugin supports RTCP Mux +// * We assume NodeJs supports RTCP Mux (e.g. 'you're on your own' mode) +// +OT.$.registerCapability('RTCPMux', function() { + return OT.$.hasCapabilities('webrtc') && + (OT.$.env.name === 'Chrome' || + OT.$.env.name === 'Node' || + OTPlugin.isInstalled()); +}); + + + +// Indicates whether this browser supports the getMediaDevices (getSources) API. +// +OT.$.registerCapability('getMediaDevices', function() { + return OT.$.isFunction(window.MediaStreamTrack) && + OT.$.isFunction(window.MediaStreamTrack.getSources); +}); + + +OT.$.registerCapability('audioOutputLevelStat', function() { + return OT.$.env.name === 'Chrome' || OT.$.env.name === 'IE'; +}); + +OT.$.registerCapability('webAudioCapableRemoteStream', function() { + return OT.$.env.name === 'Firefox'; +}); + +OT.$.registerCapability('webAudio', function() { + return 'AudioContext' in window; +}); + + +// tb_require('../helpers.js') +// tb_require('./config.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ + + +OT.Analytics = function(loggingUrl) { + + var LOG_VERSION = '1'; + var _analytics = new OT.$.Analytics(loggingUrl, OT.debug, OT._.getClientGuid); + + this.logError = function(code, type, message, details, options) { + if (!options) options = {}; + var partnerId = options.partnerId; + + if (OT.Config.get('exceptionLogging', 'enabled', partnerId) !== true) { + return; + } + + OT._.getClientGuid(function(error, guid) { + if (error) { + // @todo + return; + } + var data = OT.$.extend({ + // TODO: OT.properties.version only gives '2.2', not '2.2.9.3'. + 'clientVersion' : 'js-' + OT.properties.version.replace('v', ''), + 'guid' : guid, + 'partnerId' : partnerId, + 'source' : window.location.href, + 'logVersion' : LOG_VERSION, + 'clientSystemTime' : new Date().getTime() + }, options); + _analytics.logError(code, type, message, details, data); + }); + + }; + + this.logEvent = function(options, throttle) { + var partnerId = options.partnerId; + + if (!options) options = {}; + + OT._.getClientGuid(function(error, guid) { + if (error) { + // @todo + return; + } + + // Set a bunch of defaults + var data = OT.$.extend({ + // TODO: OT.properties.version only gives '2.2', not '2.2.9.3'. + 'clientVersion' : 'js-' + OT.properties.version.replace('v', ''), + 'guid' : guid, + 'partnerId' : partnerId, + 'source' : window.location.href, + 'logVersion' : LOG_VERSION, + 'clientSystemTime' : new Date().getTime() + }, options); + _analytics.logEvent(data, false, throttle); + }); + }; + + this.logQOS = function(options) { + var partnerId = options.partnerId; + + if (!options) options = {}; + + OT._.getClientGuid(function(error, guid) { + if (error) { + // @todo + return; + } + + // Set a bunch of defaults + var data = OT.$.extend({ + // TODO: OT.properties.version only gives '2.2', not '2.2.9.3'. + 'clientVersion' : 'js-' + OT.properties.version.replace('v', ''), + 'guid' : guid, + 'partnerId' : partnerId, + 'source' : window.location.href, + 'logVersion' : LOG_VERSION, + 'clientSystemTime' : new Date().getTime(), + 'duration' : 0 //in milliseconds + }, options); + + _analytics.logQOS(data); + }); + }; +}; + +// tb_require('../helpers.js') +// tb_require('./config.js') +// tb_require('./analytics.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ + +OT.ConnectivityAttemptPinger = function (options) { + var _state = 'Initial', + _previousState, + states = ['Initial', 'Attempt', 'Success', 'Failure'], + pingTimer, // Timer for the Attempting pings + PING_INTERVAL = 5000, + PING_COUNT_TOTAL = 6, + pingCount; + + //// Private API + var stateChanged = function(newState) { + _state = newState; + var invalidSequence = false; + switch (_state) { + case 'Attempt': + if (_previousState !== 'Initial') { + invalidSequence = true; + } + startAttemptPings(); + break; + case 'Success': + if (_previousState !== 'Attempt') { + invalidSequence = true; + } + stopAttemptPings(); + break; + case 'Failure': + if (_previousState !== 'Attempt') { + invalidSequence = true; + } + stopAttemptPings(); + break; + } + if (invalidSequence) { + var data = options ? OT.$.clone(options) : {}; + data.action = 'Internal Error'; + data.variation = 'Non-fatal'; + data.payload = { + debug: 'Invalid sequence: ' + options.action + ' ' + + _previousState + ' -> ' + _state + }; + OT.analytics.logEvent(data); + } + }, + + setState = OT.$.statable(this, states, 'Failure', stateChanged), + + startAttemptPings = function() { + pingCount = 0; + pingTimer = setInterval(function() { + if (pingCount < PING_COUNT_TOTAL) { + var data = OT.$.extend(options, {variation: 'Attempting'}); + OT.analytics.logEvent(data); + } else { + stopAttemptPings(); + } + pingCount++; + }, PING_INTERVAL); + }, + + stopAttemptPings = function() { + clearInterval(pingTimer); + }; + + this.setVariation = function(variation) { + _previousState = _state; + setState(variation); + + // We could change the ConnectivityAttemptPinger to a ConnectivityAttemptLogger + // that also logs events in addition to logging the ping ('Attempting') events. + // + // var payload = OT.$.extend(options, {variation:variation}); + // OT.analytics.logEvent(payload); + }; + + this.stop = function() { + stopAttemptPings(); + }; +}; + +// tb_require('../helpers/helpers.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ + +/* Stylable Notes + * Some bits are controlled by multiple flags, i.e. buttonDisplayMode and nameDisplayMode. + * When there are multiple flags how is the final setting chosen? + * When some style bits are set updates will need to be pushed through to the Chrome + */ + +// Mixes the StylableComponent behaviour into the +self+ object. It will +// also set the default styles to +initialStyles+. +// +// @note This Mixin is dependent on OT.Eventing. +// +// +// @example +// +// function SomeObject { +// OT.StylableComponent(this, { +// name: 'SomeObject', +// foo: 'bar' +// }); +// } +// +// var obj = new SomeObject(); +// obj.getStyle('foo'); // => 'bar' +// obj.setStyle('foo', 'baz') +// obj.getStyle('foo'); // => 'baz' +// obj.getStyle(); // => {name: 'SomeObject', foo: 'baz'} +// +OT.StylableComponent = function(self, initalStyles, showControls, logSetStyleWithPayload) { + if (!self.trigger) { + throw new Error('OT.StylableComponent is dependent on the mixin OT.$.eventing. ' + + 'Ensure that this is included in the object before StylableComponent.'); + } + + var _readOnly = false; + + // Broadcast style changes as the styleValueChanged event + var onStyleChange = function(key, value, oldValue) { + if (oldValue) { + self.trigger('styleValueChanged', key, value, oldValue); + } else { + self.trigger('styleValueChanged', key, value); + } + }; + + if(showControls === false) { + initalStyles = { + buttonDisplayMode: 'off', + nameDisplayMode: 'off', + audioLevelDisplayMode: 'off' + }; + + _readOnly = true; + logSetStyleWithPayload({ + showControls: false + }); + } + + var _style = new Style(initalStyles, onStyleChange); + +/** + * Returns an object that has the properties that define the current user interface controls of + * the Publisher. You can modify the properties of this object and pass the object to the + * setStyle() method of thePublisher object. (See the documentation for + * setStyle() to see the styles that define this object.) + * @return {Object} The object that defines the styles of the Publisher. + * @see setStyle() + * @method #getStyle + * @memberOf Publisher + */ + +/** + * Returns an object that has the properties that define the current user interface controls of + * the Subscriber. You can modify the properties of this object and pass the object to the + * setStyle() method of the Subscriber object. (See the documentation for + * setStyle() to see the styles that define this object.) + * @return {Object} The object that defines the styles of the Subscriber. + * @see setStyle() + * @method #getStyle + * @memberOf Subscriber + */ + // If +key+ is falsly then all styles will be returned. + self.getStyle = function(key) { + return _style.get(key); + }; + +/** + * Sets properties that define the appearance of some user interface controls of the Publisher. + * + *

You can either pass one parameter or two parameters to this method.

+ * + *

If you pass one parameter, style, it is an object that has the following + * properties: + * + *

    + *
  • audioLevelDisplayMode (String) — How to display the audio level + * indicator. Possible values are: "auto" (the indicator is displayed when the + * video is disabled), "off" (the indicator is not displayed), and + * "on" (the indicator is always displayed).
  • + * + *
  • backgroundImageURI (String) — A URI for an image to display as + * the background image when a video is not displayed. (A video may not be displayed if + * you call publishVideo(false) on the Publisher object). You can pass an http + * or https URI to a PNG, JPEG, or non-animated GIF file location. You can also use the + * data URI scheme (instead of http or https) and pass in base-64-encrypted + * PNG data, such as that obtained from the + * Publisher.getImgData() method. For example, + * you could set the property to "data:VBORw0KGgoAA...", where the portion of + * the string after "data:" is the result of a call to + * Publisher.getImgData(). If the URL or the image data is invalid, the + * property is ignored (the attempt to set the image fails silently). + *

    + * Note that in Internet Explorer 8 (using the OpenTok Plugin for Internet Explorer), + * you cannot set the backgroundImageURI style to a string larger than + * 32 kB. This is due to an IE 8 limitation on the size of URI strings. Due to this + * limitation, you cannot set the backgroundImageURI style to a string obtained + * with the getImgData() method. + *

  • + * + *
  • buttonDisplayMode (String) — How to display the microphone + * controls. Possible values are: "auto" (controls are displayed when the + * stream is first displayed and when the user mouses over the display), "off" + * (controls are not displayed), and "on" (controls are always displayed).
  • + * + *
  • nameDisplayMode (String) — Whether to display the stream name. + * Possible values are: "auto" (the name is displayed when the stream is first + * displayed and when the user mouses over the display), "off" (the name is not + * displayed), and "on" (the name is always displayed).
  • + *
+ *

+ * + *

For example, the following code passes one parameter to the method:

+ * + *
myPublisher.setStyle({nameDisplayMode: "off"});
+ * + *

If you pass two parameters, style and value, they are + * key-value pair that define one property of the display style. For example, the following + * code passes two parameter values to the method:

+ * + *
myPublisher.setStyle("nameDisplayMode", "off");
+ * + *

You can set the initial settings when you call the Session.publish() + * or OT.initPublisher() method. Pass a style property as part of the + * properties parameter of the method.

+ * + *

The OT object dispatches an exception event if you pass in an invalid style + * to the method. The code property of the ExceptionEvent object is set to 1011.

+ * + * @param {Object} style Either an object containing properties that define the style, or a + * String defining this single style property to set. + * @param {String} value The value to set for the style passed in. Pass a value + * for this parameter only if the value of the style parameter is a String.

+ * + * @see getStyle() + * @return {Publisher} The Publisher object + * @see setStyle() + * + * @see Session.publish() + * @see OT.initPublisher() + * @method #setStyle + * @memberOf Publisher + */ + +/** + * Sets properties that define the appearance of some user interface controls of the Subscriber. + * + *

You can either pass one parameter or two parameters to this method.

+ * + *

If you pass one parameter, style, it is an object that has the following + * properties: + * + *

    + *
  • audioLevelDisplayMode (String) — How to display the audio level + * indicator. Possible values are: "auto" (the indicator is displayed when the + * video is disabled), "off" (the indicator is not displayed), and + * "on" (the indicator is always displayed).
  • + * + *
  • backgroundImageURI (String) — A URI for an image to display as + * the background image when a video is not displayed. (A video may not be displayed if + * you call subscribeToVideo(false) on the Publisher object). You can pass an + * http or https URI to a PNG, JPEG, or non-animated GIF file location. You can also use the + * data URI scheme (instead of http or https) and pass in base-64-encrypted + * PNG data, such as that obtained from the + * Subscriber.getImgData() method. For example, + * you could set the property to "data:VBORw0KGgoAA...", where the portion of + * the string after "data:" is the result of a call to + * Publisher.getImgData(). If the URL or the image data is invalid, the + * property is ignored (the attempt to set the image fails silently). + *

    + * Note that in Internet Explorer 8 (using the OpenTok Plugin for Internet Explorer), + * you cannot set the backgroundImageURI style to a string larger than + * 32 kB. This is due to an IE 8 limitation on the size of URI strings. Due to this + * limitation, you cannot set the backgroundImageURI style to a string obtained + * with the getImgData() method. + *

  • + * + *
  • buttonDisplayMode (String) — How to display the speaker + * controls. Possible values are: "auto" (controls are displayed when the + * stream is first displayed and when the user mouses over the display), "off" + * (controls are not displayed), and "on" (controls are always displayed).
  • + * + *
  • nameDisplayMode (String) — Whether to display the stream name. + * Possible values are: "auto" (the name is displayed when the stream is first + * displayed and when the user mouses over the display), "off" (the name is not + * displayed), and "on" (the name is always displayed).
  • + * + *
  • videoDisabledDisplayMode (String) — Whether to display the video + * disabled indicator and video disabled warning icons for a Subscriber. These icons + * indicate that the video has been disabled (or is in risk of being disabled for + * the warning icon) due to poor stream quality. Possible values are: "auto" + * (the icons are automatically when the displayed video is disabled or in risk of being + * disabled due to poor stream quality), "off" (do not display the icons), and + * "on" (display the icons).
  • + *
+ *

+ * + *

For example, the following code passes one parameter to the method:

+ * + *
mySubscriber.setStyle({nameDisplayMode: "off"});
+ * + *

If you pass two parameters, style and value, they are key-value + * pair that define one property of the display style. For example, the following code passes + * two parameter values to the method:

+ * + *
mySubscriber.setStyle("nameDisplayMode", "off");
+ * + *

You can set the initial settings when you call the Session.subscribe() method. + * Pass a style property as part of the properties parameter of the + * method.

+ * + *

The OT object dispatches an exception event if you pass in an invalid style + * to the method. The code property of the ExceptionEvent object is set to 1011.

+ * + * @param {Object} style Either an object containing properties that define the style, or a + * String defining this single style property to set. + * @param {String} value The value to set for the style passed in. Pass a value + * for this parameter only if the value of the style parameter is a String.

+ * + * @returns {Subscriber} The Subscriber object. + * + * @see getStyle() + * @see setStyle() + * + * @see Session.subscribe() + * @method #setStyle + * @memberOf Subscriber + */ + + if(_readOnly) { + self.setStyle = function() { + OT.warn('Calling setStyle() has no effect because the' + + 'showControls option was set to false'); + return this; + }; + } else { + self.setStyle = function(keyOrStyleHash, value, silent) { + var logPayload = {}; + if (typeof(keyOrStyleHash) !== 'string') { + _style.setAll(keyOrStyleHash, silent); + logPayload = keyOrStyleHash; + } else { + _style.set(keyOrStyleHash, value); + logPayload[keyOrStyleHash] = value; + } + if (logSetStyleWithPayload) logSetStyleWithPayload(logPayload); + return this; + }; + } +}; + + +/*jshint latedef:false */ +var Style = function(initalStyles, onStyleChange) { +/*jshint latedef:true */ + var _style = {}, + _COMPONENT_STYLES, + _validStyleValues, + isValidStyle, + castValue; + + + _COMPONENT_STYLES = [ + 'showMicButton', + 'showSpeakerButton', + 'nameDisplayMode', + 'buttonDisplayMode', + 'backgroundImageURI', + 'audioLevelDisplayMode' + ]; + + _validStyleValues = { + buttonDisplayMode: ['auto', 'mini', 'mini-auto', 'off', 'on'], + nameDisplayMode: ['auto', 'off', 'on'], + audioLevelDisplayMode: ['auto', 'off', 'on'], + showSettingsButton: [true, false], + showMicButton: [true, false], + backgroundImageURI: null, + showControlBar: [true, false], + showArchiveStatus: [true, false], + videoDisabledDisplayMode: ['auto', 'off', 'on'] + }; + + // Validates the style +key+ and also whether +value+ is valid for +key+ + isValidStyle = function(key, value) { + return key === 'backgroundImageURI' || + (_validStyleValues.hasOwnProperty(key) && + OT.$.arrayIndexOf(_validStyleValues[key], value) !== -1 ); + }; + + castValue = function(value) { + switch(value) { + case 'true': + return true; + case 'false': + return false; + default: + return value; + } + }; + + // Returns a shallow copy of the styles. + this.getAll = function() { + var style = OT.$.clone(_style); + + for (var key in style) { + if(!style.hasOwnProperty(key)) { + continue; + } + if (OT.$.arrayIndexOf(_COMPONENT_STYLES, key) < 0) { + + // Strip unnecessary properties out, should this happen on Set? + delete style[key]; + } + } + + return style; + }; + + this.get = function(key) { + if (key) { + return _style[key]; + } + + // We haven't been asked for any specific key, just return the lot + return this.getAll(); + }; + + // *note:* this will not trigger onStyleChange if +silent+ is truthy + this.setAll = function(newStyles, silent) { + var oldValue, newValue; + + for (var key in newStyles) { + if(!newStyles.hasOwnProperty(key)) { + continue; + } + newValue = castValue(newStyles[key]); + + if (isValidStyle(key, newValue)) { + oldValue = _style[key]; + + if (newValue !== oldValue) { + _style[key] = newValue; + if (!silent) onStyleChange(key, newValue, oldValue); + } + + } else { + OT.warn('Style.setAll::Invalid style property passed ' + key + ' : ' + newValue); + } + } + + return this; + }; + + this.set = function(key, value) { + OT.debug('setStyle: ' + key.toString()); + + var newValue = castValue(value), + oldValue; + + if (!isValidStyle(key, newValue)) { + OT.warn('Style.set::Invalid style property passed ' + key + ' : ' + newValue); + return this; + } + + oldValue = _style[key]; + if (newValue !== oldValue) { + _style[key] = newValue; + + onStyleChange(key, value, oldValue); + } + + return this; + }; + + if (initalStyles) this.setAll(initalStyles, true); +}; + +// tb_require('../helpers/helpers.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ + + +// A Factory method for generating simple state machine classes. +// +// @usage +// var StateMachine = OT.generateSimpleStateMachine('start', ['start', 'middle', 'end', { +// start: ['middle'], +// middle: ['end'], +// end: ['start'] +// }]); +// +// var states = new StateMachine(); +// state.current; // <-- start +// state.set('middle'); +// +OT.generateSimpleStateMachine = function(initialState, states, transitions) { + var validStates = states.slice(), + validTransitions = OT.$.clone(transitions); + + var isValidState = function (state) { + return OT.$.arrayIndexOf(validStates, state) !== -1; + }; + + var isValidTransition = function(fromState, toState) { + return validTransitions[fromState] && + OT.$.arrayIndexOf(validTransitions[fromState], toState) !== -1; + }; + + return function(stateChangeFailed) { + var currentState = initialState, + previousState = null; + + this.current = currentState; + + function signalChangeFailed(message, newState) { + stateChangeFailed({ + message: message, + newState: newState, + currentState: currentState, + previousState: previousState + }); + } + + // Validates +newState+. If it's invalid it triggers stateChangeFailed and returns false. + function handleInvalidStateChanges(newState) { + if (!isValidState(newState)) { + signalChangeFailed('\'' + newState + '\' is not a valid state', newState); + + return false; + } + + if (!isValidTransition(currentState, newState)) { + signalChangeFailed('\'' + currentState + '\' cannot transition to \'' + + newState + '\'', newState); + + return false; + } + + return true; + } + + + this.set = function(newState) { + if (!handleInvalidStateChanges(newState)) return; + previousState = currentState; + this.current = currentState = newState; + }; + + }; +}; + +// tb_require('../helpers/helpers.js') +// tb_require('./state_machine.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ + +!(function() { + +// Models a Subscriber's subscribing State +// +// Valid States: +// NotSubscribing (the initial state +// Init (basic setup of DOM +// ConnectingToPeer (Failure Cases -> No Route, Bad Offer, Bad Answer +// BindingRemoteStream (Failure Cases -> Anything to do with the media being +// (invalid, the media never plays +// Subscribing (this is 'onLoad' +// Failed (terminal state, with a reason that maps to one of the +// (failure cases above +// Destroyed (The subscriber has been cleaned up, terminal state +// +// +// Valid Transitions: +// NotSubscribing -> +// Init +// +// Init -> +// ConnectingToPeer +// | BindingRemoteStream (if we are subscribing to ourselves and we alreay +// (have a stream +// | NotSubscribing (destroy() +// +// ConnectingToPeer -> +// BindingRemoteStream +// | NotSubscribing +// | Failed +// | NotSubscribing (destroy() +// +// BindingRemoteStream -> +// Subscribing +// | Failed +// | NotSubscribing (destroy() +// +// Subscribing -> +// NotSubscribing (unsubscribe +// | Failed (probably a peer connection failure after we began +// (subscribing +// +// Failed -> +// Destroyed +// +// Destroyed -> (terminal state) +// +// +// @example +// var state = new SubscribingState(function(change) { +// console.log(change.message); +// }); +// +// state.set('Init'); +// state.current; -> 'Init' +// +// state.set('Subscribing'); -> triggers stateChangeFailed and logs out the error message +// +// + var validStates, + validTransitions, + initialState = 'NotSubscribing'; + + validStates = [ + 'NotSubscribing', 'Init', 'ConnectingToPeer', + 'BindingRemoteStream', 'Subscribing', 'Failed', + 'Destroyed' + ]; + + validTransitions = { + NotSubscribing: ['NotSubscribing', 'Init', 'Destroyed'], + Init: ['NotSubscribing', 'ConnectingToPeer', 'BindingRemoteStream', 'Destroyed'], + ConnectingToPeer: ['NotSubscribing', 'BindingRemoteStream', 'Failed', 'Destroyed'], + BindingRemoteStream: ['NotSubscribing', 'Subscribing', 'Failed', 'Destroyed'], + Subscribing: ['NotSubscribing', 'Failed', 'Destroyed'], + Failed: ['Destroyed'], + Destroyed: [] + }; + + OT.SubscribingState = OT.generateSimpleStateMachine(initialState, validStates, validTransitions); + + OT.SubscribingState.prototype.isDestroyed = function() { + return this.current === 'Destroyed'; + }; + + OT.SubscribingState.prototype.isFailed = function() { + return this.current === 'Failed'; + }; + + OT.SubscribingState.prototype.isSubscribing = function() { + return this.current === 'Subscribing'; + }; + + OT.SubscribingState.prototype.isAttemptingToSubscribe = function() { + return OT.$.arrayIndexOf( + [ 'Init', 'ConnectingToPeer', 'BindingRemoteStream' ], + this.current + ) !== -1; + }; + +})(window); + +// tb_require('../helpers/helpers.js') +// tb_require('./state_machine.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ + +!(function() { + +// Models a Publisher's publishing State +// +// Valid States: +// NotPublishing +// GetUserMedia +// BindingMedia +// MediaBound +// PublishingToSession +// Publishing +// Failed +// Destroyed +// +// +// Valid Transitions: +// NotPublishing -> +// GetUserMedia +// +// GetUserMedia -> +// BindingMedia +// | Failed (Failure Reasons -> stream error, constraints, +// (permission denied +// | NotPublishing (destroy() +// +// +// BindingMedia -> +// MediaBound +// | Failed (Failure Reasons -> Anything to do with the media +// (being invalid, the media never plays +// | NotPublishing (destroy() +// +// MediaBound -> +// PublishingToSession (MediaBound could transition to PublishingToSession +// (if a stand-alone publish is bound to a session +// | Failed (Failure Reasons -> media issues with a stand-alone publisher +// | NotPublishing (destroy() +// +// PublishingToSession +// Publishing +// | Failed (Failure Reasons -> timeout while waiting for ack of +// (stream registered. We do not do this right now +// | NotPublishing (destroy() +// +// +// Publishing -> +// NotPublishing (Unpublish +// | Failed (Failure Reasons -> loss of network, media error, anything +// (that causes *all* Peer Connections to fail (less than all +// (failing is just an error, all is failure) +// | NotPublishing (destroy() +// +// Failed -> +// Destroyed +// +// Destroyed -> (Terminal state +// +// + + var validStates = [ + 'NotPublishing', 'GetUserMedia', 'BindingMedia', 'MediaBound', + 'PublishingToSession', 'Publishing', 'Failed', + 'Destroyed' + ], + + validTransitions = { + NotPublishing: ['NotPublishing', 'GetUserMedia', 'Destroyed'], + GetUserMedia: ['BindingMedia', 'Failed', 'NotPublishing', 'Destroyed'], + BindingMedia: ['MediaBound', 'Failed', 'NotPublishing', 'Destroyed'], + MediaBound: ['NotPublishing', 'PublishingToSession', 'Failed', 'Destroyed'], + PublishingToSession: ['NotPublishing', 'Publishing', 'Failed', 'Destroyed'], + Publishing: ['NotPublishing', 'MediaBound', 'Failed', 'Destroyed'], + Failed: ['Destroyed'], + Destroyed: [] + }, + + initialState = 'NotPublishing'; + + OT.PublishingState = OT.generateSimpleStateMachine(initialState, validStates, validTransitions); + + OT.PublishingState.prototype.isDestroyed = function() { + return this.current === 'Destroyed'; + }; + + OT.PublishingState.prototype.isAttemptingToPublish = function() { + return OT.$.arrayIndexOf( + [ 'GetUserMedia', 'BindingMedia', 'MediaBound', 'PublishingToSession' ], + this.current) !== -1; + }; + + OT.PublishingState.prototype.isPublishing = function() { + return this.current === 'Publishing'; + }; + +})(window); + +// tb_require('../helpers/helpers.js') +// tb_require('../helpers/lib/web_rtc.js') + +!(function() { + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ + +/* + * A Publishers Microphone. + * + * TODO + * * bind to changes in mute/unmute/volume/etc and respond to them + */ + OT.Microphone = function(webRTCStream, muted) { + var _muted; + + OT.$.defineProperties(this, { + muted: { + get: function() { + return _muted; + }, + set: function(muted) { + if (_muted === muted) return; + + _muted = muted; + + var audioTracks = webRTCStream.getAudioTracks(); + + for (var i=0, num=audioTracks.length; iwindow.setInterval. + * + * @param {function()} callback + * @param {number} frequency how many times per second we want to execute the callback + * @constructor + */ +OT.IntervalRunner = function(callback, frequency) { + var _callback = callback, + _frequency = frequency, + _intervalId = null; + + this.start = function() { + _intervalId = window.setInterval(_callback, 1000 / _frequency); + }; + + this.stop = function() { + window.clearInterval(_intervalId); + _intervalId = null; + }; +}; + +// tb_require('../helpers/helpers.js') + +!(function() { + /* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ + /* global OT */ + + /** + * The Event object defines the basic OpenTok event object that is passed to + * event listeners. Other OpenTok event classes implement the properties and methods of + * the Event object.

+ * + *

For example, the Stream object dispatches a streamPropertyChanged event when + * the stream's properties are updated. You add a callback for an event using the + * on() method of the Stream object:

+ * + *
+   * stream.on("streamPropertyChanged", function (event) {
+   *     alert("Properties changed for stream " + event.target.streamId);
+   * });
+ * + * @class Event + * @property {Boolean} cancelable Whether the event has a default behavior that is cancelable + * (true) or not (false). You can cancel the default behavior by + * calling the preventDefault() method of the Event object in the callback + * function. (See preventDefault().) + * + * @property {Object} target The object that dispatched the event. + * + * @property {String} type The type of event. + */ + OT.Event = OT.$.eventing.Event(); + /** + * Prevents the default behavior associated with the event from taking place. + * + *

To see whether an event has a default behavior, check the cancelable property + * of the event object.

+ * + *

Call the preventDefault() method in the callback function for the event.

+ * + *

The following events have default behaviors:

+ * + * + * + * @method #preventDefault + * @memberof Event + */ + /** + * Whether the default event behavior has been prevented via a call to + * preventDefault() (true) or not (false). + * See preventDefault(). + * @method #isDefaultPrevented + * @return {Boolean} + * @memberof Event + */ + + // Event names lookup + OT.Event.names = { + // Activity Status for cams/mics + ACTIVE: 'active', + INACTIVE: 'inactive', + UNKNOWN: 'unknown', + + // Archive types + PER_SESSION: 'perSession', + PER_STREAM: 'perStream', + + // OT Events + EXCEPTION: 'exception', + ISSUE_REPORTED: 'issueReported', + + // Session Events + SESSION_CONNECTED: 'sessionConnected', + SESSION_DISCONNECTED: 'sessionDisconnected', + STREAM_CREATED: 'streamCreated', + STREAM_DESTROYED: 'streamDestroyed', + CONNECTION_CREATED: 'connectionCreated', + CONNECTION_DESTROYED: 'connectionDestroyed', + SIGNAL: 'signal', + STREAM_PROPERTY_CHANGED: 'streamPropertyChanged', + MICROPHONE_LEVEL_CHANGED: 'microphoneLevelChanged', + + + // Publisher Events + RESIZE: 'resize', + SETTINGS_BUTTON_CLICK: 'settingsButtonClick', + DEVICE_INACTIVE: 'deviceInactive', + INVALID_DEVICE_NAME: 'invalidDeviceName', + ACCESS_ALLOWED: 'accessAllowed', + ACCESS_DENIED: 'accessDenied', + ACCESS_DIALOG_OPENED: 'accessDialogOpened', + ACCESS_DIALOG_CLOSED: 'accessDialogClosed', + ECHO_CANCELLATION_MODE_CHANGED: 'echoCancellationModeChanged', + MEDIA_STOPPED: 'mediaStopped', + PUBLISHER_DESTROYED: 'destroyed', + + // Subscriber Events + SUBSCRIBER_DESTROYED: 'destroyed', + + // DeviceManager Events + DEVICES_DETECTED: 'devicesDetected', + + // DevicePanel Events + DEVICES_SELECTED: 'devicesSelected', + CLOSE_BUTTON_CLICK: 'closeButtonClick', + + MICLEVEL : 'microphoneActivityLevel', + MICGAINCHANGED : 'microphoneGainChanged', + + // Environment Loader + ENV_LOADED: 'envLoaded', + ENV_UNLOADED: 'envUnloaded', + + // Audio activity Events + AUDIO_LEVEL_UPDATED: 'audioLevelUpdated' + }; + + OT.ExceptionCodes = { + JS_EXCEPTION: 2000, + AUTHENTICATION_ERROR: 1004, + INVALID_SESSION_ID: 1005, + CONNECT_FAILED: 1006, + CONNECT_REJECTED: 1007, + CONNECTION_TIMEOUT: 1008, + NOT_CONNECTED: 1010, + P2P_CONNECTION_FAILED: 1013, + API_RESPONSE_FAILURE: 1014, + TERMS_OF_SERVICE_FAILURE: 1026, + UNABLE_TO_PUBLISH: 1500, + UNABLE_TO_SUBSCRIBE: 1501, + UNABLE_TO_FORCE_DISCONNECT: 1520, + UNABLE_TO_FORCE_UNPUBLISH: 1530 + }; + + /** + * The {@link OT} class dispatches exception events when the OpenTok API encounters + * an exception (error). The ExceptionEvent object defines the properties of the event + * object that is dispatched. + * + *

Note that you set up a callback for the exception event by calling the + * OT.on() method.

+ * + * @class ExceptionEvent + * @property {Number} code The error code. The following is a list of error codes:

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
+ * code + * + * + * title + *
+ * 1004 + * + * + * Authentication error + *
+ * 1005 + * + * + * Invalid Session ID + *
+ * 1006 + * + * + * Connect Failed + *
+ * 1007 + * + * + * Connect Rejected + *
+ * 1008 + * + * + * Connect Time-out + *
+ * 1009 + * + * + * Security Error + *
+ * 1010 + * + * + * Not Connected + *
+ * 1011 + * + * + * Invalid Parameter + *
+ * 1013 + * + * Connection Failed + *
+ * 1014 + * + * API Response Failure + *
+ * 1026 + * + * Terms of Service Violation: Export Compliance + *
+ * 1500 + * + * Unable to Publish + *
+ * 1520 + * + * Unable to Force Disconnect + *
+ * 1530 + * + * Unable to Force Unpublish + *
+ * 1535 + * + * Force Unpublish on Invalid Stream + *
+ * 2000 + * + * + * Internal Error + *
+ * 2010 + * + * + * Report Issue Failure + *
+ * + *

Check the message property for more details about the error.

+ * + * @property {String} message The error message. + * + * @property {Object} target The object that the event pertains to. For an + * exception event, this will be an object other than the OT object + * (such as a Session object or a Publisher object). + * + * @property {String} title The error title. + * @augments Event + */ + OT.ExceptionEvent = function (type, message, title, code, component, target) { + OT.Event.call(this, type); + + this.message = message; + this.title = title; + this.code = code; + this.component = component; + this.target = target; + }; + + + OT.IssueReportedEvent = function (type, issueId) { + OT.Event.call(this, type); + this.issueId = issueId; + }; + + // Triggered when the JS dynamic config and the DOM have loaded. + OT.EnvLoadedEvent = function (type) { + OT.Event.call(this, type); + }; + + +/** + * Defines connectionCreated and connectionDestroyed events dispatched by + * the {@link Session} object. + *

+ * The Session object dispatches a connectionCreated event when a client (including + * your own) connects to a Session. It also dispatches a connectionCreated event for + * every client in the session when you first connect. (when your local client connects, the Session + * object also dispatches a sessionConnected event, defined by the + * {@link SessionConnectEvent} class.) + *

+ * While you are connected to the session, the Session object dispatches a + * connectionDestroyed event when another client disconnects from the Session. + * (When you disconnect, the Session object also dispatches a sessionDisconnected + * event, defined by the {@link SessionDisconnectEvent} class.) + * + *

Example
+ * + *

The following code keeps a running total of the number of connections to a session + * by monitoring the connections property of the sessionConnect, + * connectionCreated and connectionDestroyed events:

+ * + *
var apiKey = ""; // Replace with your API key. See https://dashboard.tokbox.com/projects
+ * var sessionID = ""; // Replace with your own session ID.
+ *                     // See https://dashboard.tokbox.com/projects
+ * var token = ""; // Replace with a generated token that has been assigned the moderator role.
+ *                 // See https://dashboard.tokbox.com/projects
+ * var connectionCount = 0;
+ *
+ * var session = OT.initSession(apiKey, sessionID);
+ * session.on("connectionCreated", function(event) {
+ *    connectionCount++;
+ *    displayConnectionCount();
+ * });
+ * session.on("connectionDestroyed", function(event) {
+ *    connectionCount--;
+ *    displayConnectionCount();
+ * });
+ * session.connect(token);
+ *
+ * function displayConnectionCount() {
+ *     document.getElementById("connectionCountField").value = connectionCount.toString();
+ * }
+ * + *

This example assumes that there is an input text field in the HTML DOM + * with the id set to "connectionCountField":

+ * + *
<input type="text" id="connectionCountField" value="0"></input>
+ * + * + * @property {Connection} connection A Connection objects for the connections that was + * created or deleted. + * + * @property {Array} connections Deprecated. Use the connection property. A + * connectionCreated or connectionDestroyed event is dispatched + * for each connection created and destroyed in the session. + * + * @property {String} reason For a connectionDestroyed event, + * a description of why the connection ended. This property can have two values: + *

+ *
    + *
  • "clientDisconnected" — A client disconnected from the session by calling + * the disconnect() method of the Session object or by closing the browser. + * (See Session.disconnect().)
  • + * + *
  • "forceDisconnected" — A moderator has disconnected the publisher + * from the session, by calling the forceDisconnect() method of the Session + * object. (See Session.forceDisconnect().)
  • + * + *
  • "networkDisconnected" — The network connection terminated abruptly + * (for example, the client lost their internet connection).
  • + *
+ * + *

Depending on the context, this description may allow the developer to refine + * the course of action they take in response to an event.

+ * + *

For a connectionCreated event, this string is undefined.

+ * + * @class ConnectionEvent + * @augments Event + */ + var connectionEventPluralDeprecationWarningShown = false; + OT.ConnectionEvent = function (type, connection, reason) { + OT.Event.call(this, type, false); + + if (OT.$.canDefineProperty) { + Object.defineProperty(this, 'connections', { + get: function() { + if(!connectionEventPluralDeprecationWarningShown) { + OT.warn('OT.ConnectionEvent connections property is deprecated, ' + + 'use connection instead.'); + connectionEventPluralDeprecationWarningShown = true; + } + return [connection]; + } + }); + } else { + this.connections = [connection]; + } + + this.connection = connection; + this.reason = reason; + }; + +/** + * StreamEvent is an event that can have the type "streamCreated" or "streamDestroyed". + * These events are dispatched by the Session object when another client starts or + * stops publishing a stream to a {@link Session}. For a local client's stream, the + * Publisher object dispatches the event. + * + *

Example — streamCreated event dispatched + * by the Session object

+ *

The following code initializes a session and sets up an event listener for when + * a stream published by another client is created:

+ * + *
+ * session.on("streamCreated", function(event) {
+ *   // streamContainer is a DOM element
+ *   subscriber = session.subscribe(event.stream, targetElement);
+ * }).connect(token);
+ * 
+ * + *

Example — streamDestroyed event dispatched + * by the Session object

+ * + *

The following code initializes a session and sets up an event listener for when + * other clients' streams end:

+ * + *
+ * session.on("streamDestroyed", function(event) {
+ *     console.log("Stream " + event.stream.name + " ended. " + event.reason);
+ * }).connect(token);
+ * 
+ * + *

Example — streamCreated event dispatched + * by a Publisher object

+ *

The following code publishes a stream and adds an event listener for when the streaming + * starts

+ * + *
+ * var publisher = session.publish(targetElement)
+ *   .on("streamCreated", function(event) {
+ *     console.log("Publisher started streaming.");
+ *   );
+ * 
+ * + *

Example — streamDestroyed event + * dispatched by a Publisher object

+ * + *

The following code publishes a stream, and leaves the Publisher in the HTML DOM + * when the streaming stops:

+ * + *
+ * var publisher = session.publish(targetElement)
+ *   .on("streamDestroyed", function(event) {
+ *     event.preventDefault();
+ *     console.log("Publisher stopped streaming.");
+ *   );
+ * 
+ * + * @class StreamEvent + * + * @property {Boolean} cancelable Whether the event has a default behavior that is cancelable + * (true) or not (false). You can cancel the default behavior by calling + * the preventDefault() method of the StreamEvent object in the event listener + * function. The streamDestroyed + * event is cancelable. (See preventDefault().) + * + * @property {String} reason For a streamDestroyed event, + * a description of why the session disconnected. This property can have one of the following + * values: + *

+ *
    + *
  • "clientDisconnected" — A client disconnected from the session by calling + * the disconnect() method of the Session object or by closing the browser. + * (See Session.disconnect().)
  • + * + *
  • "forceDisconnected" — A moderator has disconnected the publisher of the + * stream from the session, by calling the forceDisconnect() method of the Session +* object. (See Session.forceDisconnect().)
  • + * + *
  • "forceUnpublished" — A moderator has forced the publisher of the stream + * to stop publishing the stream, by calling the forceUnpublish() method of the + * Session object. (See Session.forceUnpublish().)
  • + * + *
  • "mediaStopped" — The user publishing the stream has stopped sharing the + * screen. This value is only used in screen-sharing video streams.
  • + * + *
  • "networkDisconnected" — The network connection terminated abruptly (for + * example, the client lost their internet connection).
  • + * + *
+ * + *

Depending on the context, this description may allow the developer to refine + * the course of action they take in response to an event.

+ * + *

For a streamCreated event, this string is undefined.

+ * + * @property {Stream} stream A Stream object corresponding to the stream that was added (in the + * case of a streamCreated event) or deleted (in the case of a + * streamDestroyed event). + * + * @property {Array} streams Deprecated. Use the stream property. A + * streamCreated or streamDestroyed event is dispatched for + * each stream added or destroyed. + * + * @augments Event + */ + + var streamEventPluralDeprecationWarningShown = false; + OT.StreamEvent = function (type, stream, reason, cancelable) { + OT.Event.call(this, type, cancelable); + + if (OT.$.canDefineProperty) { + Object.defineProperty(this, 'streams', { + get: function() { + if(!streamEventPluralDeprecationWarningShown) { + OT.warn('OT.StreamEvent streams property is deprecated, use stream instead.'); + streamEventPluralDeprecationWarningShown = true; + } + return [stream]; + } + }); + } else { + this.streams = [stream]; + } + + this.stream = stream; + this.reason = reason; + }; + +/** +* Prevents the default behavior associated with the event from taking place. +* +*

For the streamDestroyed event dispatched by the Session object, +* the default behavior is that all Subscriber objects that are subscribed to the stream are +* unsubscribed and removed from the HTML DOM. Each Subscriber object dispatches a +* destroyed event when the element is removed from the HTML DOM. If you call the +* preventDefault() method in the event listener for the streamDestroyed +* event, the default behavior is prevented and you can clean up Subscriber objects using your +* own code. See +* Session.getSubscribersForStream().

+*

+* For the streamDestroyed event dispatched by a Publisher object, the default +* behavior is that the Publisher object is removed from the HTML DOM. The Publisher object +* dispatches a destroyed event when the element is removed from the HTML DOM. +* If you call the preventDefault() method in the event listener for the +* streamDestroyed event, the default behavior is prevented, and you can +* retain the Publisher for reuse or clean it up using your own code. +*

+*

To see whether an event has a default behavior, check the cancelable property of +* the event object.

+* +*

Call the preventDefault() method in the event listener function for the event.

+* +* @method #preventDefault +* @memberof StreamEvent +*/ + +/** + * The Session object dispatches SessionConnectEvent object when a session has successfully + * connected in response to a call to the connect() method of the Session object. + *

+ * In version 2.2, the completionHandler of the Session.connect() method + * indicates success or failure in connecting to the session. + * + * @class SessionConnectEvent + * @property {Array} connections Deprecated in version 2.2 (and set to an empty array). In + * version 2.2, listen for the connectionCreated event dispatched by the Session + * object. In version 2.2, the Session object dispatches a connectionCreated event + * for each connection (including your own). This includes connections present when you first + * connect to the session. + * + * @property {Array} streams Deprecated in version 2.2 (and set to an empty array). In version + * 2.2, listen for the streamCreated event dispatched by the Session object. In + * version 2.2, the Session object dispatches a streamCreated event for each stream + * other than those published by your client. This includes streams + * present when you first connect to the session. + * + * @see Session.connect()

+ * @augments Event + */ + + var sessionConnectedConnectionsDeprecationWarningShown = false; + var sessionConnectedStreamsDeprecationWarningShown = false; + var sessionConnectedArchivesDeprecationWarningShown = false; + + OT.SessionConnectEvent = function (type) { + OT.Event.call(this, type, false); + if (OT.$.canDefineProperty) { + Object.defineProperties(this, { + connections: { + get: function() { + if(!sessionConnectedConnectionsDeprecationWarningShown) { + OT.warn('OT.SessionConnectedEvent no longer includes connections. Listen ' + + 'for connectionCreated events instead.'); + sessionConnectedConnectionsDeprecationWarningShown = true; + } + return []; + } + }, + streams: { + get: function() { + if(!sessionConnectedStreamsDeprecationWarningShown) { + OT.warn('OT.SessionConnectedEvent no longer includes streams. Listen for ' + + 'streamCreated events instead.'); + sessionConnectedConnectionsDeprecationWarningShown = true; + } + return []; + } + }, + archives: { + get: function() { + if(!sessionConnectedArchivesDeprecationWarningShown) { + OT.warn('OT.SessionConnectedEvent no longer includes archives. Listen for ' + + 'archiveStarted events instead.'); + sessionConnectedArchivesDeprecationWarningShown = true; + } + return []; + } + } + }); + } else { + this.connections = []; + this.streams = []; + this.archives = []; + } + }; + +/** + * The Session object dispatches SessionDisconnectEvent object when a session has disconnected. + * This event may be dispatched asynchronously in response to a successful call to the + * disconnect() method of the session object. + * + *

+ * Example + *

+ *

+ * The following code initializes a session and sets up an event listener for when a session is + * disconnected. + *

+ *
var apiKey = ""; // Replace with your API key. See https://dashboard.tokbox.com/projects
+ *  var sessionID = ""; // Replace with your own session ID.
+ *                      // See https://dashboard.tokbox.com/projects
+ *  var token = ""; // Replace with a generated token that has been assigned the moderator role.
+ *                  // See https://dashboard.tokbox.com/projects
+ *
+ *  var session = OT.initSession(apiKey, sessionID);
+ *  session.on("sessionDisconnected", function(event) {
+ *      alert("The session disconnected. " + event.reason);
+ *  });
+ *  session.connect(token);
+ *  
+ * + * @property {String} reason A description of why the session disconnected. + * This property can have two values: + *

+ *
    + *
  • "clientDisconnected" — A client disconnected from the session by calling + * the disconnect() method of the Session object or by closing the browser. + * ( See Session.disconnect().)
  • + *
  • "forceDisconnected" — A moderator has disconnected you from the session + * by calling the forceDisconnect() method of the Session object. (See + * Session.forceDisconnect().)
  • + *
  • "networkDisconnected" — The network connection terminated abruptly + * (for example, the client lost their internet connection).
  • + *
+ * + * @class SessionDisconnectEvent + * @augments Event + */ + OT.SessionDisconnectEvent = function (type, reason, cancelable) { + OT.Event.call(this, type, cancelable); + this.reason = reason; + }; + +/** +* Prevents the default behavior associated with the event from taking place. +* +*

For the sessionDisconnectEvent, the default behavior is that all Subscriber +* objects are unsubscribed and removed from the HTML DOM. Each Subscriber object dispatches a +* destroyed event when the element is removed from the HTML DOM. If you call the +* preventDefault() method in the event listener for the sessionDisconnect +* event, the default behavior is prevented, and you can, optionally, clean up Subscriber objects +* using your own code). +* +*

To see whether an event has a default behavior, check the cancelable property of +* the event object.

+* +*

Call the preventDefault() method in the event listener function for the event.

+* +* @method #preventDefault +* @memberof SessionDisconnectEvent +*/ + +/** + * The Session object dispatches a streamPropertyChanged event in the + * following circumstances: + * + *
    + *
  • A stream has started or stopped publishing audio or video (see + * Publisher.publishAudio() and + * Publisher.publishVideo()). + * This change results from a call to the publishAudio() or + * publishVideo() methods of the Publish object. Note that a + * subscriber's video can be disabled or enabled for reasons other than the + * publisher disabling or enabling it. A Subscriber object dispatches + * videoDisabled and videoEnabled events in all + * conditions that cause the subscriber's stream to be disabled or enabled. + *
  • + *
  • The videoDimensions property of the Stream object has + * changed (see Stream.videoDimensions). + *
  • + *
  • The videoType property of the Stream object has changed. + * This can happen in a stream published by a mobile device. (See + * Stream.videoType.) + *
  • + *
+ * + * @class StreamPropertyChangedEvent + * @property {String} changedProperty The property of the stream that changed. This value + * is either "hasAudio", "hasVideo", or "videoDimensions". + * @property {Object} newValue The new value of the property (after the change). + * @property {Object} oldValue The old value of the property (before the change). + * @property {Stream} stream The Stream object for which a property has changed. + * + * @see Publisher.publishAudio()

+ * @see Publisher.publishVideo()

+ * @see Stream.videoDimensions

+ * @augments Event + */ + OT.StreamPropertyChangedEvent = function (type, stream, changedProperty, oldValue, newValue) { + OT.Event.call(this, type, false); + this.type = type; + this.stream = stream; + this.changedProperty = changedProperty; + this.oldValue = oldValue; + this.newValue = newValue; + }; + + OT.VideoDimensionsChangedEvent = function (target, oldValue, newValue) { + OT.Event.call(this, 'videoDimensionsChanged', false); + this.type = 'videoDimensionsChanged'; + this.target = target; + this.oldValue = oldValue; + this.newValue = newValue; + }; + +/** + * Defines event objects for the archiveStarted and archiveStopped events. + * The Session object dispatches these events when an archive recording of the session starts and + * stops. + * + * @property {String} id The archive ID. + * @property {String} name The name of the archive. You can assign an archive a name when you create + * it, using the OpenTok REST API or one of the + * OpenTok server SDKs. + * + * @class ArchiveEvent + * @augments Event + */ + OT.ArchiveEvent = function (type, archive) { + OT.Event.call(this, type, false); + this.type = type; + this.id = archive.id; + this.name = archive.name; + this.status = archive.status; + this.archive = archive; + }; + + OT.ArchiveUpdatedEvent = function (stream, key, oldValue, newValue) { + OT.Event.call(this, 'updated', false); + this.target = stream; + this.changedProperty = key; + this.oldValue = oldValue; + this.newValue = newValue; + }; + +/** + * The Session object dispatches a signal event when the client receives a signal from the session. + * + * @class SignalEvent + * @property {String} type The type assigned to the signal (if there is one). Use the type to + * filter signals received (by adding an event handler for signal:type1 or signal:type2, etc.) + * @property {String} data The data string sent with the signal (if there is one). + * @property {Connection} from The Connection corresponding to the client that sent with the signal. + * + * @see Session.signal()

+ * @see Session events (signal and signal:type)

+ * @augments Event + */ + OT.SignalEvent = function(type, data, from) { + OT.Event.call(this, type ? 'signal:' + type : OT.Event.names.SIGNAL, false); + this.data = data; + this.from = from; + }; + + OT.StreamUpdatedEvent = function (stream, key, oldValue, newValue) { + OT.Event.call(this, 'updated', false); + this.target = stream; + this.changedProperty = key; + this.oldValue = oldValue; + this.newValue = newValue; + }; + + OT.DestroyedEvent = function(type, target, reason) { + OT.Event.call(this, type, false); + this.target = target; + this.reason = reason; + }; + +/** + * Defines the event object for the videoDisabled and videoEnabled events + * dispatched by the Subscriber. + * + * @class VideoEnabledChangedEvent + * + * @property {Boolean} cancelable Whether the event has a default behavior that is cancelable + * (true) or not (false). You can cancel the default behavior by + * calling the preventDefault() method of the event object in the callback + * function. (See preventDefault().) + * + * @property {String} reason The reason the video was disabled or enabled. This can be set to one of + * the following values: + * + *
    + * + *
  • "publishVideo" — The publisher started or stopped publishing video, + * by calling publishVideo(true) or publishVideo(false).
  • + * + *
  • "quality" — The OpenTok Media Router starts or stops sending video + * to the subscriber based on stream quality changes. This feature of the OpenTok Media + * Router has a subscriber drop the video stream when connectivity degrades. (The subscriber + * continues to receive the audio stream, if there is one.) + *

    + * If connectivity improves to support video again, the Subscriber object dispatches + * a videoEnabled event, and the Subscriber resumes receiving video. + *

    + * By default, the Subscriber displays a video disabled indicator when a + * videoDisabled event with this reason is dispatched and removes the indicator + * when the videoDisabled event with this reason is dispatched. You can control + * the display of this icon by calling the setStyle() method of the Subscriber, + * setting the videoDisabledDisplayMode property(or you can set the style when + * calling the Session.subscribe() method, setting the style property + * of the properties parameter). + *

    + * This feature is only available in sessions that use the OpenTok Media Router (sessions with + * the media mode + * set to routed), not in sessions with the media mode set to relayed. + *

  • + * + *
  • "subscribeToVideo" — The subscriber started or stopped subscribing to + * video, by calling subscribeToVideo(true) or subscribeToVideo(false). + *
  • + * + *
+ * + * @property {Object} target The object that dispatched the event. + * + * @property {String} type The type of event: "videoDisabled" or + * "videoEnabled". + * + * @see Subscriber videoDisabled event

+ * @see Subscriber videoEnabled event

+ * @augments Event + */ + OT.VideoEnabledChangedEvent = function(type, properties) { + OT.Event.call(this, type, false); + this.reason = properties.reason; + }; + + OT.VideoDisableWarningEvent = function(type/*, properties*/) { + OT.Event.call(this, type, false); + }; + +/** + * Dispatched periodically by a Subscriber or Publisher object to indicate the audio + * level. This event is dispatched up to 60 times per second, depending on the browser. + * + * @property {String} audioLevel The audio level, from 0 to 1.0. Adjust this value logarithmically + * for use in adjusting a user interface element, such as a volume meter. Use a moving average + * to smooth the data. + * + * @class AudioLevelUpdatedEvent + * @augments Event + */ + OT.AudioLevelUpdatedEvent = function(audioLevel) { + OT.Event.call(this, OT.Event.names.AUDIO_LEVEL_UPDATED, false); + this.audioLevel = audioLevel; + }; + + OT.MediaStoppedEvent = function(target) { + OT.Event.call(this, OT.Event.names.MEDIA_STOPPED, true); + this.target = target; + }; + +})(window); + +// tb_require('../../helpers/helpers.js') +// tb_require('../events.js') + +var screenSharingExtensionByKind = {}, + screenSharingExtensionClasses = {}; + +OT.registerScreenSharingExtensionHelper = function(kind, helper) { + screenSharingExtensionClasses[kind] = helper; + if (helper.autoRegisters && helper.isSupportedInThisBrowser) { + OT.registerScreenSharingExtension(kind); + } +}; + +/** + * Register a Chrome extension for screen-sharing support. + *

+ * Use the OT.checkScreenSharingCapability() method to check if an extension is + * required, registered, and installed. + *

+ * The OpenTok + * screensharing-extensions + * sample includes code for creating a Chrome extension for screen-sharing support. + * + * @param {String} kind Set this parameter to "chrome". Currently, you can only + * register a screen-sharing extension for Chrome. + * + * @see OT.initPublisher() + * @see OT.checkScreenSharingCapability() + * @method OT.registerScreenSharingExtension + * @memberof OT + */ + +OT.registerScreenSharingExtension = function(kind) { + var initArgs = Array.prototype.slice.call(arguments, 1); + + if (screenSharingExtensionClasses[kind] == null) { + throw new Error('Unsupported kind passed to OT.registerScreenSharingExtension'); + } + + var x = screenSharingExtensionClasses[kind] + .register.apply(screenSharingExtensionClasses[kind], initArgs); + screenSharingExtensionByKind[kind] = x; +}; + +var screenSharingPickHelper = function() { + + var foundClass = OT.$.find(OT.$.keys(screenSharingExtensionClasses), function(cls) { + return screenSharingExtensionClasses[cls].isSupportedInThisBrowser; + }); + + if (foundClass === void 0) { + return {}; + } + + return { + name: foundClass, + proto: screenSharingExtensionClasses[foundClass], + instance: screenSharingExtensionByKind[foundClass] + }; + +}; + +OT.pickScreenSharingHelper = function() { + return screenSharingPickHelper(); +}; + +/** + * Checks for screen sharing support on the client browser. The object passed to the callback + * function defines whether screen sharing is supported as well as whether an extension is + * required, installed, and registered (if needed). + *

+ *

+ * OT.checkScreenSharingCapability(function(response) {
+ *   if (!response.supported || response.extensionRegistered === false) {
+ *     // This browser does not support screen sharing
+ *   } else if(response.extensionInstalled === false) {
+ *     // Prompt to install the extension
+ *   } else {
+ *     // Screen sharing is available.
+ *   }
+ * });
+ * 
+ * + * @param {function} callback The callback invoked with the support options object passed as + * the parameter. This object has the following properties: + *

+ *

    + *
  • + * supported (Boolean) — Set to true if screen sharing is supported in the + * browser. Check the extensionRequired property to see if the browser requires + * an extension for screen sharing. + *
  • + *
  • + * extensionRequired (String) — Set to "chrome" on Chrome, + * which requires a screen sharing extension to be installed. Otherwise, this property is + * undefined. + *
  • + *
  • + * extensionRegistered (Boolean) — On Chrome, this property is set to + * true if a screen-sharing extension is registered; otherwise it is set to + * false. If the extension type does not require registration (as in the + * case of of the OpenTok plugin for Internet Explorer), this property is set to + * true. In other browsers (which do not require an extension), this property + * is undefined. Use the OT.registerScreenSharingExtension() method to register + * an extension in Chrome. + *
  • + *
  • + * extensionInstalled (Boolean) — If an extension is required, this is set + * to true if the extension installed (and registered, if needed); otherwise it + * is set to false. If an extension is not required (for example on FireFox), + * this property is undefined. + *
  • + *
+ * + * @see OT.initPublisher() + * @see OT.registerScreenSharingExtension() + * @method OT.checkScreenSharingCapability + * @memberof OT + */ +OT.checkScreenSharingCapability = function(callback) { + + var response = { + supported: false, + extensionRequired: void 0, + extensionRegistered: void 0, + extensionInstalled: void 0, + supportedSources: {} + }; + + // find a supported browser + + var helper = screenSharingPickHelper(); + + if (helper.name === void 0) { + setTimeout(callback.bind(null, response)); + return; + } + + response.supported = true; + response.extensionRequired = helper.proto.extensionRequired ? helper.name : void 0; + + response.supportedSources = { + screen: helper.proto.sources.screen, + application: helper.proto.sources.application, + window: helper.proto.sources.window + }; + + if (!helper.instance) { + response.extensionRegistered = false; + if (response.extensionRequired) { + response.extensionInstalled = false; + } + setTimeout(callback.bind(null, response)); + return; + } + + response.extensionRegistered = response.extensionRequired ? true : void 0; + helper.instance.isInstalled(function(installed) { + response.extensionInstalled = response.extensionRequired ? installed : void 0; + callback(response); + }); + +}; + +// tb_require('./register.js') + +OT.registerScreenSharingExtensionHelper('firefox', { + isSupportedInThisBrowser: OT.$.env.name === 'Firefox', + autoRegisters: true, + extensionRequired: false, + getConstraintsShowsPermissionUI: false, + sources: { + screen: true, + application: OT.$.env.name === 'Firefox' && OT.$.env.version >= 34, + window: OT.$.env.name === 'Firefox' && OT.$.env.version >= 34 + }, + register: function() { + return { + isInstalled: function(callback) { + callback(true); + }, + getConstraints: function(source, constraints, callback) { + constraints.video = { + mediaSource: source + }; + callback(void 0, constraints); + } + }; + } +}); + +OT.registerScreenSharingExtensionHelper('chrome', { + isSupportedInThisBrowser: !!navigator.webkitGetUserMedia && typeof chrome !== 'undefined', + autoRegisters: false, + extensionRequired: true, + getConstraintsShowsPermissionUI: true, + sources: { + screen: true, + application: false, + window: false + }, + register: function (extensionID) { + if(!extensionID) { + throw new Error('initChromeScreenSharingExtensionHelper: extensionID is required.'); + } + + var isChrome = !!navigator.webkitGetUserMedia && typeof chrome !== 'undefined', + callbackRegistry = {}, + isInstalled = void 0; + + var prefix = 'com.tokbox.screenSharing.' + extensionID; + var request = function(method, payload) { + var res = { payload: payload, from: 'jsapi' }; + res[prefix] = method; + return res; + }; + + var addCallback = function(fn, timeToWait) { + var requestId = OT.$.uuid(), + timeout; + callbackRegistry[requestId] = function() { + clearTimeout(timeout); + timeout = null; + fn.apply(null, arguments); + }; + if(timeToWait) { + timeout = setTimeout(function() { + delete callbackRegistry[requestId]; + fn(new Error('Timeout waiting for response to request.')); + }, timeToWait); + } + return requestId; + }; + + var isAvailable = function(callback) { + if(!callback) { + throw new Error('isAvailable: callback is required.'); + } + + if(!isChrome) { + setTimeout(callback.bind(null, false)); + } + + if(isInstalled !== void 0) { + setTimeout(callback.bind(null, isInstalled)); + } else { + var requestId = addCallback(function(error, event) { + if(isInstalled !== true) { + isInstalled = (event === 'extensionLoaded'); + } + callback(isInstalled); + }, 2000); + var post = request('isExtensionInstalled', { requestId: requestId }); + window.postMessage(post, '*'); + } + }; + + var getConstraints = function(source, constraints, callback) { + if(!callback) { + throw new Error('getSourceId: callback is required'); + } + isAvailable(function(isInstalled) { + if(isInstalled) { + var requestId = addCallback(function(error, event, payload) { + if(event === 'permissionDenied') { + callback(new Error('PermissionDeniedError')); + } else { + if (!constraints.video) { + constraints.video = {}; + } + if (!constraints.video.mandatory) { + constraints.video.mandatory = {}; + } + constraints.video.mandatory.chromeMediaSource = 'desktop'; + constraints.video.mandatory.chromeMediaSourceId = payload.sourceId; + callback(void 0, constraints); + } + }); + window.postMessage(request('getSourceId', { requestId: requestId, source: source }), '*'); + } else { + callback(new Error('Extension is not installed')); + } + }); + }; + + window.addEventListener('message', function(event) { + + if (event.origin !== window.location.origin) { + return; + } + + if(!(event.data != null && typeof event.data === 'object')) { + return; + } + + if(event.data.from !== 'extension') { + return; + } + + var method = event.data[prefix], + payload = event.data.payload; + + if(payload && payload.requestId) { + var callback = callbackRegistry[payload.requestId]; + delete callbackRegistry[payload.requestId]; + if(callback) { + callback(null, method, payload); + } + } + + if(method === 'extensionLoaded') { + isInstalled = true; + } + }); + + return { + isInstalled: isAvailable, + getConstraints: getConstraints + }; + } +}); + +// tb_require('../helpers/helpers.js') +// tb_require('../helpers/lib/properties.js') +// tb_require('../helpers/lib/video_element.js') +// tb_require('./events.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ + + +// id: String | mandatory | immutable +// type: String {video/audio/data/...} | mandatory | immutable +// active: Boolean | mandatory | mutable +// orientation: Integer? | optional | mutable +// frameRate: Float | optional | mutable +// height: Integer | optional | mutable +// width: Integer | optional | mutable +OT.StreamChannel = function(options) { + this.id = options.id; + this.type = options.type; + this.active = OT.$.castToBoolean(options.active); + this.orientation = options.orientation || OT.VideoOrientation.ROTATED_NORMAL; + if (options.frameRate) this.frameRate = parseFloat(options.frameRate, 10); + this.width = parseInt(options.width, 10); + this.height = parseInt(options.height, 10); + + // The defaults are used for incoming streams from pre 2015Q1 release clients. + this.source = options.source || 'camera'; + this.fitMode = options.fitMode || 'cover'; + + OT.$.eventing(this, true); + + // Returns true if a property was updated. + this.update = function(attributes) { + var videoDimensions = {}, + oldVideoDimensions = {}; + + for (var key in attributes) { + if(!attributes.hasOwnProperty(key)) { + continue; + } + + // we shouldn't really read this before we know the key is valid + var oldValue = this[key]; + + switch(key) { + case 'active': + this.active = OT.$.castToBoolean(attributes[key]); + break; + + case 'disableWarning': + this.disableWarning = OT.$.castToBoolean(attributes[key]); + break; + + case 'frameRate': + this.frameRate = parseFloat(attributes[key], 10); + break; + + case 'width': + case 'height': + this[key] = parseInt(attributes[key], 10); + + videoDimensions[key] = this[key]; + oldVideoDimensions[key] = oldValue; + break; + + case 'orientation': + this[key] = attributes[key]; + + videoDimensions[key] = this[key]; + oldVideoDimensions[key] = oldValue; + break; + + case 'fitMode': + this[key] = attributes[key]; + break; + + case 'source': + this[key] = attributes[key]; + break; + + default: + OT.warn('Tried to update unknown key ' + key + ' on ' + this.type + + ' channel ' + this.id); + return; + } + + this.trigger('update', this, key, oldValue, this[key]); + } + + if (OT.$.keys(videoDimensions).length) { + // To make things easier for the public API, we broadcast videoDimensions changes, + // which is an aggregate of width, height, and orientation changes. + this.trigger('update', this, 'videoDimensions', oldVideoDimensions, videoDimensions); + } + + return true; + }; +}; + +// tb_require('../helpers/helpers.js') +// tb_require('../helpers/lib/properties.js') +// tb_require('./events.js') +// tb_require('./stream_channel.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ + +!(function() { + + var validPropertyNames = ['name', 'archiving']; + +/** + * Specifies a stream. A stream is a representation of a published stream in a session. When a + * client calls the Session.publish() method, a new stream is + * created. Properties of the Stream object provide information about the stream. + * + *

When a stream is added to a session, the Session object dispatches a + * streamCreatedEvent. When a stream is destroyed, the Session object dispatches a + * streamDestroyed event. The StreamEvent object, which defines these event objects, + * has a stream property, which is an array of Stream object. For details and a code + * example, see {@link StreamEvent}.

+ * + *

When a connection to a session is made, the Session object dispatches a + * sessionConnected event, defined by the SessionConnectEvent object. The + * SessionConnectEvent object has a streams property, which is an array of Stream + * objects pertaining to the streams in the session at that time. For details and a code example, + * see {@link SessionConnectEvent}.

+ * + * @class Stream + * @property {Connection} connection The Connection object corresponding + * to the connection that is publishing the stream. You can compare this to to the + * connection property of the Session object to see if the stream is being published + * by the local web page. + * + * @property {Number} creationTime The timestamp for the creation + * of the stream. This value is calculated in milliseconds. You can convert this value to a + * Date object by calling new Date(creationTime), where creationTime is + * the creationTime property of the Stream object. + * + * @property {Number} frameRate The frame rate of the video stream. This property is only set if the + * publisher of the stream specifies a frame rate when calling the OT.initPublisher() + * method; otherwise, this property is undefined. + * + * @property {Boolean} hasAudio Whether the stream has audio. This property can change if the + * publisher turns on or off audio (by calling + * Publisher.publishAudio()). When this occurs, the + * {@link Session} object dispatches a streamPropertyChanged event (see + * {@link StreamPropertyChangedEvent}). + * + * @property {Boolean} hasVideo Whether the stream has video. This property can change if the + * publisher turns on or off video (by calling + * Publisher.publishVideo()). When this occurs, the + * {@link Session} object dispatches a streamPropertyChanged event (see + * {@link StreamPropertyChangedEvent}). + * + * @property {String} name The name of the stream. Publishers can specify a name when publishing + * a stream (using the publish() method of the publisher's Session object). + * + * @property {String} streamId The unique ID of the stream. + * + * @property {Object} videoDimensions This object has two properties: width and + * height. Both are numbers. The width property is the width of the + * encoded stream; the height property is the height of the encoded stream. (These + * are independent of the actual width of Publisher and Subscriber objects corresponding to the + * stream.) This property can change if a stream published from a mobile device resizes, based on + * a change in the device orientation. It can also occur if the video source is a screen-sharing + * window and the user publishing the stream resizes the window. When the video dimensions change, + * the {@link Session} object dispatches a streamPropertyChanged event + * (see {@link StreamPropertyChangedEvent}). + * + * @property {String} videoType The type of video — either "camera" or + * "screen". A "screen" video uses screen sharing on the publisher + * as the video source; for other videos, this property is set to "camera". + * This property can change if a stream published from a mobile device changes from a + * camera to a screen-sharing video type. When the video type changes, the {@link Session} object + * dispatches a streamPropertyChanged event (see {@link StreamPropertyChangedEvent}). + */ + + + OT.Stream = function(id, name, creationTime, connection, session, channel) { + var destroyedReason; + + this.id = this.streamId = id; + this.name = name; + this.creationTime = Number(creationTime); + + this.connection = connection; + this.channel = channel; + this.publisher = OT.publishers.find({streamId: this.id}); + + OT.$.eventing(this); + + var onChannelUpdate = OT.$.bind(function(channel, key, oldValue, newValue) { + var _key = key; + + switch(_key) { + case 'active': + _key = channel.type === 'audio' ? 'hasAudio' : 'hasVideo'; + this[_key] = newValue; + break; + + case 'disableWarning': + _key = channel.type === 'audio' ? 'audioDisableWarning': 'videoDisableWarning'; + this[_key] = newValue; + if (!this[channel.type === 'audio' ? 'hasAudio' : 'hasVideo']) { + return; // Do NOT event in this case. + } + break; + + case 'fitMode': + _key = 'defaultFitMode'; + this[_key] = newValue; + break; + + case 'source': + _key = channel.type === 'audio' ? 'audioType' : 'videoType'; + this[_key] = newValue; + break; + + case 'orientation': + case 'width': + case 'height': + this.videoDimensions = { + width: channel.width, + height: channel.height, + orientation: channel.orientation + }; + + // We dispatch this via the videoDimensions key instead + return; + } + + this.dispatchEvent( new OT.StreamUpdatedEvent(this, _key, oldValue, newValue) ); + }, this); + + var associatedWidget = OT.$.bind(function() { + if(this.publisher) { + return this.publisher; + } else { + return OT.subscribers.find(function(subscriber) { + return subscriber.stream.id === this.id && + subscriber.session.id === session.id; + }); + } + }, this); + + // Returns all channels that have a type of +type+. + this.getChannelsOfType = function (type) { + return OT.$.filter(this.channel, function(channel) { + return channel.type === type; + }); + }; + + this.getChannel = function (id) { + for (var i=0; i * * - * Errors when calling Session.subscribe(): - * - * - * - * * code * * Description @@ -14005,7 +17324,7 @@ waitForDomReady(); * * * - *

Errors when calling TB.initPublisher():

+ *

Errors when calling OT.initPublisher():

* * * @@ -14020,6 +17339,32 @@ waitForDomReady(); * the current version of one of the * OpenTok server SDKs. * + * + * + * + * + * + * + * + * + * + * + * + * *
1550Screen sharing is not supported (and you set the videoSource property + * of the options parameter of OT.initPublisher() to + * "screen"). Before calling OT.initPublisher(), you can call + * OT.checkScreenSharingCapability() + * to check if screen sharing is supported.
1551A screen sharing extension needs to be registered but it is not. This error can occur + * when you set the videoSource property of the options parameter + * of OT.initPublisher() to "screen". Before calling + * OT.initPublisher(), you can call + * OT.checkScreenSharingCapability() + * to check if screen sharing requires an extension to be registered.
1552A screen sharing extension is required, but it is not installed. This error can occur + * when you set the videoSource property of the options parameter + * of OT.initPublisher() to "screen". Before calling + * OT.initPublisher(), you can call + * OT.checkScreenSharingCapability() + * to check if screen sharing requires an extension to be installed.
* *

General errors that can occur when calling any method:

@@ -14062,17 +17407,21 @@ waitForDomReady(); 1012: 'Peer-to-peer Stream Play Failed', 1013: 'Connection Failed', 1014: 'API Response Failure', + 1015: 'Session connected, cannot test network', + 1021: 'Request Timeout', + 1026: 'Terms of Service Violation: Export Compliance', 1500: 'Unable to Publish', + 1503: 'No TURN server found', 1520: 'Unable to Force Disconnect', 1530: 'Unable to Force Unpublish', + 1553: 'ICEWorkflow failed', + 1600: 'createOffer, createAnswer, setLocalDescription, setRemoteDescription', 2000: 'Internal Error', - 2001: 'Embed Failed', + 2001: 'Unexpected HTTP error codes (f.e. 500)', 4000: 'WebSocket Connection Failed', 4001: 'WebSocket Network Disconnected' }; - var analytics; - function _exceptionHandler(component, msg, errorCode, context) { var title = errorsCodesToTitle[errorCode], contextCopy = context ? OT.$.clone(context) : {}; @@ -14082,8 +17431,7 @@ waitForDomReady(); if (!contextCopy.partnerId) contextCopy.partnerId = OT.APIKEY; try { - if (!analytics) analytics = new OT.Analytics(); - analytics.logError(errorCode, 'tb.exception', title, {details:msg}, contextCopy); + OT.analytics.logError(errorCode, 'tb.exception', title, {details:msg}, contextCopy); OT.dispatchEvent( new OT.ExceptionEvent(OT.Event.names.EXCEPTION, msg, title, errorCode, component, component) @@ -14143,3399 +17491,4515 @@ waitForDomReady(); }; })(window); + +// tb_require('../helpers/helpers.js') +// tb_require('../helpers/lib/config.js') +// tb_require('./events.js') + !(function() { - OT.ConnectionCapabilities = function(capabilitiesHash) { - // Private helper methods - var castCapabilities = function(capabilitiesHash) { - capabilitiesHash.supportsWebRTC = OT.$.castToBoolean(capabilitiesHash.supportsWebRTC); - return capabilitiesHash; - }; + /* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ + /* global OT */ - // Private data - var _caps = castCapabilities(capabilitiesHash); - this.supportsWebRTC = _caps.supportsWebRTC; - }; + // Helper to synchronise several startup tasks and then dispatch a unified + // 'envLoaded' event. + // + // This depends on: + // * OT + // * OT.Config + // + function EnvironmentLoader() { + var _configReady = false, -})(window); -!(function() { + // If the plugin is installed, then we should wait for it to + // be ready as well. + _pluginSupported = OTPlugin.isSupported(), + _pluginLoadAttemptComplete = _pluginSupported ? OTPlugin.isReady() : true, - /** - * The Connection object represents a connection to an OpenTok session. Each client that connects - * to a session has a unique connection, with a unique connection ID (represented by the - * id property of the Connection object for the client). - *

- * The Session object has a connection property that is a Connection object. - * It represents the local client's connection. (A client only has a connection once the - * client has successfully called the connect() method of the {@link Session} - * object.) - *

- * The Session object dispatches a connectionCreated event when each client - * (including your own) connects to a session (and for clients that are present in the - * session when you connect). The connectionCreated event object has a - * connection property, which is a Connection object corresponding to the client - * the event pertains to. - *

- * The Stream object has a connection property that is a Connection object. - * It represents the connection of the client that is publishing the stream. - * - * @class Connection - * @property {String} connectionId The ID of this connection. - * @property {Number} creationTime The timestamp for the creation of the connection. This - * value is calculated in milliseconds. - * You can convert this value to a Date object by calling new Date(creationTime), - * where creationTime - * is the creationTime property of the Connection object. - * @property {String} data A string containing metadata describing the - * connection. When you generate a user token string pass the connection data string to the - * generate_token() method of our - * server-side libraries. You can also generate a token - * and define connection data on the - * Dashboard page. - */ - OT.Connection = function(id, creationTime, data, capabilitiesHash, permissionsHash) { - var destroyedReason; + isReady = function() { + return !OT.$.isDOMUnloaded() && OT.$.isReady() && + _configReady && _pluginLoadAttemptComplete; + }, - this.id = this.connectionId = id; - this.creationTime = creationTime ? Number(creationTime) : null; - this.data = data; - this.capabilities = new OT.ConnectionCapabilities(capabilitiesHash); - this.permissions = new OT.Capabilities(permissionsHash); - this.quality = null; - - OT.$.eventing(this); - - this.destroy = OT.$.bind(function(reason, quiet) { - destroyedReason = reason || 'clientDisconnected'; - - if (quiet !== true) { - this.dispatchEvent( - new OT.DestroyedEvent( - 'destroyed', // This should be OT.Event.names.CONNECTION_DESTROYED, but - // the value of that is currently shared with Session - this, - destroyedReason - ) - ); - } - }, this); - - this.destroyed = function() { - return destroyedReason !== void 0; - }; - - this.destroyedReason = function() { - return destroyedReason; - }; - - }; - - OT.Connection.fromHash = function(hash) { - return new OT.Connection(hash.id, - hash.creationTime, - hash.data, - OT.$.extend(hash.capablities || {}, { supportsWebRTC: true }), - hash.permissions || [] ); - }; - -})(window); -!(function() { - - // id: String | mandatory | immutable - // type: String {video/audio/data/...} | mandatory | immutable - // active: Boolean | mandatory | mutable - // orientation: Integer? | optional | mutable - // frameRate: Float | optional | mutable - // height: Integer | optional | mutable - // width: Integer | optional | mutable - OT.StreamChannel = function(options) { - this.id = options.id; - this.type = options.type; - this.active = OT.$.castToBoolean(options.active); - this.orientation = options.orientation || OT.VideoOrientation.ROTATED_NORMAL; - if (options.frameRate) this.frameRate = parseFloat(options.frameRate, 10); - this.width = parseInt(options.width, 10); - this.height = parseInt(options.height, 10); - - OT.$.eventing(this, true); - - // Returns true if a property was updated. - this.update = function(attributes) { - var videoDimensions = {}, - oldVideoDimensions = {}; - - for (var key in attributes) { - if(!attributes.hasOwnProperty(key)) { - continue; - } - // we shouldn't really read this before we know the key is valid - var oldValue = this[key]; - - switch(key) { - case 'active': - this.active = OT.$.castToBoolean(attributes[key]); - break; - - case 'disableWarning': - this.disableWarning = OT.$.castToBoolean(attributes[key]); - break; - - case 'frameRate': - this.frameRate = parseFloat(attributes[key], 10); - break; - - case 'width': - case 'height': - this[key] = parseInt(attributes[key], 10); - - videoDimensions[key] = this[key]; - oldVideoDimensions[key] = oldValue; - break; - - case 'orientation': - this[key] = attributes[key]; - - videoDimensions[key] = this[key]; - oldVideoDimensions[key] = oldValue; - break; - - default: - OT.warn('Tried to update unknown key ' + key + ' on ' + this.type + - ' channel ' + this.id); - return; - } - - this.trigger('update', this, key, oldValue, this[key]); - } - - if (OT.$.keys(videoDimensions).length) { - // To make things easier for the public API, we broadcast videoDimensions changes, - // which is an aggregate of width, height, and orientation changes. - this.trigger('update', this, 'videoDimensions', oldVideoDimensions, videoDimensions); - } - - return true; - }; - }; - -})(window); -!(function() { - - var validPropertyNames = ['name', 'archiving']; - -/** - * Specifies a stream. A stream is a representation of a published stream in a session. When a - * client calls the Session.publish() method, a new stream is - * created. Properties of the Stream object provide information about the stream. - * - *

When a stream is added to a session, the Session object dispatches a - * streamCreatedEvent. When a stream is destroyed, the Session object dispatches a - * streamDestroyed event. The StreamEvent object, which defines these event objects, - * has a stream property, which is an array of Stream object. For details and a code - * example, see {@link StreamEvent}.

- * - *

When a connection to a session is made, the Session object dispatches a - * sessionConnected event, defined by the SessionConnectEvent object. The - * SessionConnectEvent object has a streams property, which is an array of Stream - * objects pertaining to the streams in the session at that time. For details and a code example, - * see {@link SessionConnectEvent}.

- * - * @class Stream - * @property {Connection} connection The Connection object corresponding - * to the connection that is publishing the stream. You can compare this to to the - * connection property of the Session object to see if the stream is being published - * by the local web page. - * - * @property {Number} creationTime The timestamp for the creation - * of the stream. This value is calculated in milliseconds. You can convert this value to a - * Date object by calling new Date(creationTime), where creationTime is - * the creationTime property of the Stream object. - * - * @property {Number} frameRate The frame rate of the video stream. This property is only set if the - * publisher of the stream specifies a frame rate when calling the OT.initPublisher() - * method; otherwise, this property is undefined. - * - * @property {Boolean} hasAudio Whether the stream has audio. This property can change if the - * publisher turns on or off audio (by calling - * Publisher.publishAudio()). When this occurs, the - * {@link Session} object dispatches a streamPropertyChanged event (see - * {@link StreamPropertyChangedEvent}.) - * - * @property {Boolean} hasVideo Whether the stream has video. This property can change if the - * publisher turns on or off video (by calling - * Publisher.publishVideo()). When this occurs, the - * {@link Session} object dispatches a streamPropertyChanged event (see - * {@link StreamPropertyChangedEvent}.) - * - * @property {String} name The name of the stream. Publishers can specify a name when publishing - * a stream (using the publish() method of the publisher's Session object). - * - * @property {String} streamId The unique ID of the stream. - * - * @property {Object} videoDimensions This object has two properties: width and - * height. Both are numbers. The width property is the width of the - * encoded stream; the height property is the height of the encoded stream. (These - * are independent of the actual width of Publisher and Subscriber objects corresponding to the - * stream.) This property can change if a stream - * published from an iOS device resizes, based on a change in the device orientation. When this - * occurs, the {@link Session} object dispatches a streamPropertyChanged event (see - * {@link StreamPropertyChangedEvent}.) - */ - - - OT.Stream = function(id, name, creationTime, connection, session, channel) { - var destroyedReason; - - this.id = this.streamId = id; - this.name = name; - this.creationTime = Number(creationTime); - - this.connection = connection; - this.channel = channel; - this.publisher = OT.publishers.find({streamId: this.id}); - - OT.$.eventing(this); - - var onChannelUpdate = OT.$.bind(function(channel, key, oldValue, newValue) { - var _key = key; - - switch(_key) { - case 'active': - _key = channel.type === 'audio' ? 'hasAudio' : 'hasVideo'; - this[_key] = newValue; - break; - - case 'disableWarning': - _key = channel.type === 'audio' ? 'audioDisableWarning': 'videoDisableWarning'; - this[_key] = newValue; - if (!this[channel.type === 'audio' ? 'hasAudio' : 'hasVideo']) { - return; // Do NOT event in this case. + onLoaded = function() { + if (isReady()) { + OT.dispatchEvent(new OT.EnvLoadedEvent(OT.Event.names.ENV_LOADED)); } - break; - - case 'orientation': - case 'width': - case 'height': - this.videoDimensions = { - width: channel.width, - height: channel.height, - orientation: channel.orientation - }; - - // We dispatch this via the videoDimensions key instead - return; - } - - this.dispatchEvent( new OT.StreamUpdatedEvent(this, _key, oldValue, newValue) ); - }, this); - - var associatedWidget = OT.$.bind(function() { - if(this.publisher) { - return this.publisher; - } else { - return OT.subscribers.find(function(subscriber) { - return subscriber.stream.id === this.id && - subscriber.session.id === session.id; - }); - } - }, this); - - // Returns all channels that have a type of +type+. - this.getChannelsOfType = function (type) { - return OT.$.filter(this.channel, function(channel) { - return channel.type === type; - }); - }; - - this.getChannel = function (id) { - for (var i=0; iOT.upgradeSystemRequirements() + * @method OT.checkSystemRequirements + * @memberof OT + */ +OT.checkSystemRequirements = function() { + OT.debug('OT.checkSystemRequirements()'); + + // Try native support first, then OTPlugin... + var systemRequirementsMet = OT.$.hasCapabilities('websockets', 'webrtc') || + OTPlugin.isInstalled(); + + systemRequirementsMet = systemRequirementsMet ? + this.HAS_REQUIREMENTS : this.NOT_HAS_REQUIREMENTS; + + OT.checkSystemRequirements = function() { + OT.debug('OT.checkSystemRequirements()'); + return systemRequirementsMet; + }; + + if(systemRequirementsMet === this.NOT_HAS_REQUIREMENTS) { + OT.analytics.logEvent({ + action: 'checkSystemRequirements', + variation: 'notHasRequirements', + partnerId: OT.APIKEY, + payload: {userAgent: OT.$.env.userAgent} + }); + } + + return systemRequirementsMet; +}; - /* - * A RTCPeerConnection.getStats based audio level sampler. - * - * It uses the the getStats method to get the audioOutputLevel. - * This implementation expects the single parameter version of the getStats method. - * - * Currently the audioOutputLevel stats is only supported in Chrome. - * - * @param {OT.SubscriberPeerConnection} peerConnection the peer connection to use to get the stats - * @constructor - */ - OT.GetStatsAudioLevelSampler = function(peerConnection) { +/** + * Displays information about system requirments for OpenTok for WebRTC. This + * information is displayed in an iframe element that fills the browser window. + *

+ * Note: this information is displayed automatically when you call the + * OT.initSession() or the OT.initPublisher() method + * if the client does not support OpenTok for WebRTC. + *

+ * @see OT.checkSystemRequirements() + * @method OT.upgradeSystemRequirements + * @memberof OT + */ +OT.upgradeSystemRequirements = function(){ + // trigger after the OT environment has loaded + OT.onLoad( function() { - if (!OT.$.hasCapabilities('audioOutputLevelStat', 'getStatsWithSingleParameter')) { - throw new Error('The current platform does not provide the required capabilities'); + if(OTPlugin.isSupported()) { + OT.Dialogs.Plugin.promptToInstall().on({ + download: function() { + window.location = OTPlugin.pathToInstaller(); + }, + refresh: function() { + location.reload(); + }, + closed: function() {} + }); + return; } - var _peerConnection = peerConnection, - _statsProperty = 'audioOutputLevel'; + var id = '_upgradeFlash'; - /* - * Acquires the audio level. - * - * @param {function(?number)} done a callback to be called with the acquired value in the - * [0, 1] range when available or null if no value could be acquired - */ - this.sample = function(done) { - _peerConnection.getStatsWithSingleParameter(function(statsReport) { - var results = statsReport.result(); + // Load the iframe over the whole page. + document.body.appendChild((function() { + var d = document.createElement('iframe'); + d.id = id; + d.style.position = 'absolute'; + d.style.position = 'fixed'; + d.style.height = '100%'; + d.style.width = '100%'; + d.style.top = '0px'; + d.style.left = '0px'; + d.style.right = '0px'; + d.style.bottom = '0px'; + d.style.zIndex = 1000; + try { + d.style.backgroundColor = 'rgba(0,0,0,0.2)'; + } catch (err) { + // Old IE browsers don't support rgba and we still want to show the upgrade message + // but we just make the background of the iframe completely transparent. + d.style.backgroundColor = 'transparent'; + d.setAttribute('allowTransparency', 'true'); + } + d.setAttribute('frameBorder', '0'); + d.frameBorder = '0'; + d.scrolling = 'no'; + d.setAttribute('scrolling', 'no'); - for (var i = 0; i < results.length; i++) { - var result = results[i]; - if (result.local) { - var audioOutputLevel = parseFloat(result.local.stat(_statsProperty)); - if (!isNaN(audioOutputLevel)) { - // the mex value delivered by getStats for audio levels is 2^15 - done(audioOutputLevel / 32768); - return; - } - } + var minimumBrowserVersion = OT.properties.minimumVersion[OT.$.env.name.toLowerCase()], + isSupportedButOld = minimumBrowserVersion > OT.$.env.version; + d.src = OT.properties.assetURL + '/html/upgrade.html#' + + encodeURIComponent(isSupportedButOld ? 'true' : 'false') + ',' + + encodeURIComponent(JSON.stringify(OT.properties.minimumVersion)) + '|' + + encodeURIComponent(document.location.href); + + return d; + })()); + + // Now we need to listen to the event handler if the user closes this dialog. + // Since this is from an IFRAME within another domain we are going to listen to hash + // changes. The best cross browser solution is to poll for a change in the hashtag. + if (_intervalId) clearInterval(_intervalId); + _intervalId = setInterval(function(){ + var hash = document.location.hash, + re = /^#?\d+&/; + if (hash !== _lastHash && re.test(hash)) { + _lastHash = hash; + if (hash.replace(re, '') === 'close_window'){ + document.body.removeChild(document.getElementById(id)); + document.location.hash = ''; } - - done(null); - }); - }; - }; - - - /* - * An AudioContext based audio level sampler. It returns the maximum value in the - * last 1024 samples. - * - * It is worth noting that the remote MediaStream audio analysis is currently only - * available in FF. - * - * This implementation gracefully handles the case where the MediaStream has not - * been set yet by returning a null value until the stream is set. It is up to the - * call site to decide what to do with this value (most likely ignore it and retry later). - * - * @constructor - * @param {AudioContext} audioContext an audio context instance to get an analyser node - */ - OT.AnalyserAudioLevelSampler = function(audioContext) { - - var _sampler = this, - _analyser = null, - _timeDomainData = null; - - var _getAnalyser = function(stream) { - var sourceNode = audioContext.createMediaStreamSource(stream); - var analyser = audioContext.createAnalyser(); - sourceNode.connect(analyser); - return analyser; - }; - - this.webOTStream = null; - - this.sample = function(done) { - - if (!_analyser && _sampler.webOTStream) { - _analyser = _getAnalyser(_sampler.webOTStream); - _timeDomainData = new Uint8Array(_analyser.frequencyBinCount); } - - if (_analyser) { - _analyser.getByteTimeDomainData(_timeDomainData); - - // varies from 0 to 255 - var max = 0; - for (var idx = 0; idx < _timeDomainData.length; idx++) { - max = Math.max(max, Math.abs(_timeDomainData[idx] - 128)); - } - - // normalize the collected level to match the range delivered by - // the getStats' audioOutputLevel - done(max / 128); - } else { - done(null); - } - }; - }; - - /* - * Transforms a raw audio level to produce a "smoother" animation when using displaying the - * audio level. This transformer is state-full because it needs to keep the previous average - * value of the signal for filtering. - * - * It applies a low pass filter to get rid of level jumps and apply a log scale. - * - * @constructor - */ - OT.AudioLevelTransformer = function() { - - var _averageAudioLevel = null; - - /* - * - * @param {number} audioLevel a level in the [0,1] range - * @returns {number} a level in the [0,1] range transformed - */ - this.transform = function(audioLevel) { - if (_averageAudioLevel === null || audioLevel >= _averageAudioLevel) { - _averageAudioLevel = audioLevel; - } else { - // a simple low pass filter with a smoothing of 70 - _averageAudioLevel = audioLevel * 0.3 + _averageAudioLevel * 0.7; - } - - // 1.5 scaling to map -30-0 dBm range to [0,1] - var logScaled = (Math.log(_averageAudioLevel) / Math.LN10) / 1.5 + 1; - - return Math.min(Math.max(logScaled, 0), 1); - }; - }; - -})(window); -!(function() { - - /* - * Executes the provided callback thanks to window.setInterval. - * - * @param {function()} callback - * @param {number} frequency how many times per second we want to execute the callback - * @constructor - */ - OT.IntervalRunner = function(callback, frequency) { - var _callback = callback, - _frequency = frequency, - _intervalId = null; - - this.start = function() { - _intervalId = window.setInterval(_callback, 1000 / _frequency); - }; - - this.stop = function() { - window.clearInterval(_intervalId); - _intervalId = null; - }; - }; - -})(window); -// tb_require('../../helpers/helpers.js') + }, 100); + }); +}; +// tb_require('../helpers/helpers.js') /* jshint globalstrict: true, strict: false, undef: true, unused: true, trailing: true, browser: true, smarttabs:true */ /* global OT */ -/* exported SDPHelpers */ -var findIndex = function(array, iter, ctx) { - if (!OT.$.isFunction(iter)) { - throw new TypeError('iter must be a function'); - } +OT.ConnectionCapabilities = function(capabilitiesHash) { + // Private helper methods + var castCapabilities = function(capabilitiesHash) { + capabilitiesHash.supportsWebRTC = OT.$.castToBoolean(capabilitiesHash.supportsWebRTC); + return capabilitiesHash; + }; - for (var i = 0, count = array.length || 0; i < count; ++i) { - if (i in array && iter.call(ctx, array[i], i, array)) { - return i; - } - } - - return -1; + // Private data + var _caps = castCapabilities(capabilitiesHash); + this.supportsWebRTC = _caps.supportsWebRTC; }; -// Here are the structure of the rtpmap attribute and the media line, most of the -// complex Regular Expressions in this code are matching against one of these two -// formats: -// * a=rtpmap: / [/] -// * m= / -// -// References: -// * https://tools.ietf.org/html/rfc4566 -// * http://en.wikipedia.org/wiki/Session_Description_Protocol -// -var SDPHelpers = { - // Search through sdpLines to find the Media Line of type +mediaType+. - getMLineIndex: function getMLineIndex(sdpLines, mediaType) { - var targetMLine = 'm=' + mediaType; +// tb_require('../helpers/helpers.js') +// tb_require('../helpers/lib/properties.js') - // Find the index of the media line for +type+ - return findIndex(sdpLines, function(line) { - if (line.indexOf(targetMLine) !== -1) { - return true; - } +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ - return false; - }); - }, - // Extract the payload types for a give Media Line. - // - getMLinePayloadTypes: function getMLinePayloadTypes (mediaLine, mediaType) { - var mLineSelector = new RegExp('^m=' + mediaType + - ' \\d+(/\\d+)? [a-zA-Z0-9/]+(( [a-zA-Z0-9/]+)+)$', 'i'); +/** + * A class defining properties of the capabilities property of a + * Session object. See Session.capabilities. + *

+ * All Capabilities properties are undefined until you have connected to a session + * and the Session object has dispatched the sessionConnected event. + *

+ * For more information on token roles, see the + * generate_token() + * method of the OpenTok server-side libraries. + * + * @class Capabilities + * + * @property {Number} forceDisconnect Specifies whether you can call + * the Session.forceDisconnect() method (1) or not (0). To call the + * Session.forceDisconnect() method, + * the user must have a token that is assigned the role of moderator. + * @property {Number} forceUnpublish Specifies whether you can call + * the Session.forceUnpublish() method (1) or not (0). To call the + * Session.forceUnpublish() method, the user must have a token that + * is assigned the role of moderator. + * @property {Number} publish Specifies whether you can publish to the session (1) or not (0). + * The ability to publish is based on a few factors. To publish, the user must have a token that + * is assigned a role that supports publishing. There must be a connected camera and microphone. + * @property {Number} subscribe Specifies whether you can subscribe to streams + * in the session (1) or not (0). Currently, this capability is available for all users on all + * platforms. + */ +OT.Capabilities = function(permissions) { + this.publish = OT.$.arrayIndexOf(permissions, 'publish') !== -1 ? 1 : 0; + this.subscribe = OT.$.arrayIndexOf(permissions, 'subscribe') !== -1 ? 1 : 0; + this.forceUnpublish = OT.$.arrayIndexOf(permissions, 'forceunpublish') !== -1 ? 1 : 0; + this.forceDisconnect = OT.$.arrayIndexOf(permissions, 'forcedisconnect') !== -1 ? 1 : 0; + this.supportsWebRTC = OT.$.hasCapabilities('webrtc') ? 1 : 0; - // Get all payload types that the line supports - var payloadTypes = mediaLine.match(mLineSelector); - if (!payloadTypes || payloadTypes.length < 2) { - // Error, invalid M line? - return []; + this.permittedTo = function(action) { + return this.hasOwnProperty(action) && this[action] === 1; + }; +}; + +// tb_require('../helpers/helpers.js') +// tb_require('../helpers/lib/properties.js') +// tb_require('./events.js') +// tb_require('./capabilities.js') +// tb_require('./connection_capabilities.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ + +/** + * The Connection object represents a connection to an OpenTok session. Each client that connects + * to a session has a unique connection, with a unique connection ID (represented by the + * id property of the Connection object for the client). + *

+ * The Session object has a connection property that is a Connection object. + * It represents the local client's connection. (A client only has a connection once the + * client has successfully called the connect() method of the {@link Session} + * object.) + *

+ * The Session object dispatches a connectionCreated event when each client (including + * your own) connects to a session (and for clients that are present in the session when you + * connect). The connectionCreated event object has a connection + * property, which is a Connection object corresponding to the client the event pertains to. + *

+ * The Stream object has a connection property that is a Connection object. + * It represents the connection of the client that is publishing the stream. + * + * @class Connection + * @property {String} connectionId The ID of this connection. + * @property {Number} creationTime The timestamp for the creation of the connection. This + * value is calculated in milliseconds. + * You can convert this value to a Date object by calling new Date(creationTime), + * where creationTime + * is the creationTime property of the Connection object. + * @property {String} data A string containing metadata describing the + * connection. When you generate a user token string pass the connection data string to the + * generate_token() method of our + * server-side libraries. You can also generate a token + * and define connection data on the + * Dashboard page. + */ +OT.Connection = function(id, creationTime, data, capabilitiesHash, permissionsHash) { + var destroyedReason; + + this.id = this.connectionId = id; + this.creationTime = creationTime ? Number(creationTime) : null; + this.data = data; + this.capabilities = new OT.ConnectionCapabilities(capabilitiesHash); + this.permissions = new OT.Capabilities(permissionsHash); + this.quality = null; + + OT.$.eventing(this); + + this.destroy = OT.$.bind(function(reason, quiet) { + destroyedReason = reason || 'clientDisconnected'; + + if (quiet !== true) { + this.dispatchEvent( + new OT.DestroyedEvent( + 'destroyed', // This should be OT.Event.names.CONNECTION_DESTROYED, but + // the value of that is currently shared with Session + this, + destroyedReason + ) + ); } + }, this); - return OT.$.trim(payloadTypes[2]).split(' '); - }, + this.destroyed = function() { + return destroyedReason !== void 0; + }; - removeTypesFromMLine: function removeTypesFromMLine (mediaLine, payloadTypes) { - return mediaLine.replace(new RegExp(' ' + payloadTypes.join(' |'), 'ig') , '') - .replace(/\s+/g, ' '); - }, + this.destroyedReason = function() { + return destroyedReason; + }; +}; - // Remove all references to a particular encodingName from a particular media type - // - removeMediaEncoding: function removeMediaEncoding (sdp, mediaType, encodingName) { - var sdpLines = sdp.split('\r\n'), - mLineIndex = SDPHelpers.getMLineIndex(sdpLines, mediaType), - mLine = mLineIndex > -1 ? sdpLines[mLineIndex] : void 0, - typesToRemove = [], - payloadTypes, - match; - - if (mLineIndex === -1) { - // Error, missing M line - return sdpLines.join('\r\n'); - } - - // Get all payload types that the line supports - payloadTypes = SDPHelpers.getMLinePayloadTypes(mLine, mediaType); - if (payloadTypes.length === 0) { - // Error, invalid M line? - return sdpLines.join('\r\n'); - } - - // Find the location of all the rtpmap lines that relate to +encodingName+ - // and any of the supported payload types - var matcher = new RegExp('a=rtpmap:(' + payloadTypes.join('|') + ') ' + - encodingName + '\\/\\d+', 'i'); - - sdpLines = OT.$.filter(sdpLines, function(line, index) { - match = line.match(matcher); - if (match === null) return true; - - typesToRemove.push(match[1]); - - if (index < mLineIndex) { - // This removal changed the index of the mline, track it - mLineIndex--; - } - - // remove this one - return false; - }); - - if (typesToRemove.length > 0 && mLineIndex > -1) { - // Remove all the payload types and we've removed from the media line - sdpLines[mLineIndex] = SDPHelpers.removeTypesFromMLine(mLine, typesToRemove); - } - - return sdpLines.join('\r\n'); - }, - - // Removes all Confort Noise from +sdp+. - // - // See https://jira.tokbox.com/browse/OPENTOK-7176 - // - removeComfortNoise: function removeComfortNoise (sdp) { - return SDPHelpers.removeMediaEncoding(sdp, 'audio', 'CN'); - }, - - removeVideoCodec: function removeVideoCodec (sdp, codec) { - return SDPHelpers.removeMediaEncoding(sdp, 'video', codec); - } +OT.Connection.fromHash = function(hash) { + return new OT.Connection(hash.id, + hash.creationTime, + hash.data, + OT.$.extend(hash.capablities || {}, { supportsWebRTC: true }), + hash.permissions || [] ); }; -!(function(window) { - /* global SDPHelpers */ +// tb_require('../../../helpers/helpers.js') +// tb_require('./message.js') +// tb_require('../../connection.js') - // Normalise these - var NativeRTCSessionDescription, - NativeRTCIceCandidate; +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ - if (!TBPlugin.isInstalled()) { - // order is very important: 'RTCSessionDescription' defined in Firefox Nighly but useless - NativeRTCSessionDescription = (window.mozRTCSessionDescription || - window.RTCSessionDescription); - NativeRTCIceCandidate = (window.mozRTCIceCandidate || window.RTCIceCandidate); - } - else { - NativeRTCSessionDescription = TBPlugin.RTCSessionDescription; - NativeRTCIceCandidate = TBPlugin.RTCIceCandidate; - } - - // Helper function to forward Ice Candidates via +messageDelegate+ - var iceCandidateForwarder = function(messageDelegate) { - return function(event) { - if (event.candidate) { - messageDelegate(OT.Raptor.Actions.CANDIDATE, event.candidate); - } else { - OT.debug('IceCandidateForwarder: No more ICE candidates.'); - } - }; - }; - - - // Process incoming Ice Candidates from a remote connection (which have been - // forwarded via iceCandidateForwarder). The Ice Candidates cannot be processed - // until a PeerConnection is available. Once a PeerConnection becomes available - // the pending PeerConnections can be processed by calling processPending. - // - // @example - // - // var iceProcessor = new IceCandidateProcessor(); - // iceProcessor.process(iceMessage1); - // iceProcessor.process(iceMessage2); - // iceProcessor.process(iceMessage3); - // - // iceProcessor.setPeerConnection(peerConnection); - // iceProcessor.processPending(); - // - var IceCandidateProcessor = function() { - var _pendingIceCandidates = [], - _peerConnection = null; - - this.setPeerConnection = function(peerConnection) { - _peerConnection = peerConnection; - }; - - this.process = function(message) { - var iceCandidate = new NativeRTCIceCandidate(message.content); - - if (_peerConnection) { - _peerConnection.addIceCandidate(iceCandidate); - } else { - _pendingIceCandidates.push(iceCandidate); - } - }; - - this.processPending = function() { - while(_pendingIceCandidates.length) { - _peerConnection.addIceCandidate(_pendingIceCandidates.shift()); - } - }; - }; - - - // Attempt to completely process +offer+. This will: - // * set the offer as the remote description - // * create an answer and - // * set the new answer as the location description - // - // If there are no issues, the +success+ callback will be executed on completion. - // Errors during any step will result in the +failure+ callback being executed. - // - var offerProcessor = function(peerConnection, offer, success, failure) { - var generateErrorCallback, - setLocalDescription, - createAnswer; - - generateErrorCallback = function(message, prefix) { - return function(errorReason) { - OT.error(message); - OT.error(errorReason); - - if (failure) failure(message, errorReason, prefix); - }; - }; - - setLocalDescription = function(answer) { - answer.sdp = SDPHelpers.removeComfortNoise(answer.sdp); - answer.sdp = SDPHelpers.removeVideoCodec(answer.sdp, 'ulpfec'); - answer.sdp = SDPHelpers.removeVideoCodec(answer.sdp, 'red'); - - peerConnection.setLocalDescription( - answer, - - // Success - function() { - success(answer); - }, - - // Failure - generateErrorCallback('Error while setting LocalDescription', 'SetLocalDescription') - ); - }; - - createAnswer = function() { - peerConnection.createAnswer( - // Success - setLocalDescription, - - // Failure - generateErrorCallback('Error while setting createAnswer', 'CreateAnswer'), - - null, // MediaConstraints - false // createProvisionalAnswer - ); - }; - - // Workaround for a Chrome issue. Add in the SDES crypto line into offers - // from Firefox - if (offer.sdp.indexOf('a=crypto') === -1) { - var cryptoLine = 'a=crypto:1 AES_CM_128_HMAC_SHA1_80 ' + - 'inline:FakeFakeFakeFakeFakeFakeFakeFakeFakeFake\\r\\n'; - - // insert the fake crypto line for every M line - offer.sdp = offer.sdp.replace(/^c=IN(.*)$/gmi, 'c=IN$1\r\n'+cryptoLine); - } - - if (offer.sdp.indexOf('a=rtcp-fb') === -1) { - var rtcpFbLine = 'a=rtcp-fb:* ccm fir\r\na=rtcp-fb:* nack '; - - // insert the fake crypto line for every M line - offer.sdp = offer.sdp.replace(/^m=video(.*)$/gmi, 'm=video$1\r\n'+rtcpFbLine); - } - - peerConnection.setRemoteDescription( - offer, - - // Success - createAnswer, - - // Failure - generateErrorCallback('Error while setting RemoteDescription', 'SetRemoteDescription') - ); - - }; - - // Attempt to completely process a subscribe message. This will: - // * create an Offer - // * set the new offer as the location description - // - // If there are no issues, the +success+ callback will be executed on completion. - // Errors during any step will result in the +failure+ callback being executed. - // - var suscribeProcessor = function(peerConnection, success, failure) { - var constraints, - generateErrorCallback, - setLocalDescription; - - constraints = { - mandatory: {}, - optional: [] - }, - - generateErrorCallback = function(message, prefix) { - return function(errorReason) { - OT.error(message); - OT.error(errorReason); - - if (failure) failure(message, errorReason, prefix); - }; - }; - - setLocalDescription = function(offer) { - offer.sdp = SDPHelpers.removeComfortNoise(offer.sdp); - offer.sdp = SDPHelpers.removeVideoCodec(offer.sdp, 'ulpfec'); - offer.sdp = SDPHelpers.removeVideoCodec(offer.sdp, 'red'); - - - peerConnection.setLocalDescription( - offer, - - // Success - function() { - success(offer); - }, - - // Failure - generateErrorCallback('Error while setting LocalDescription', 'SetLocalDescription') - ); - }; - - // For interop with FireFox. Disable Data Channel in createOffer. - if (navigator.mozGetUserMedia) { - constraints.mandatory.MozDontOfferDataChannel = true; - } - - peerConnection.createOffer( - // Success - setLocalDescription, - - // Failure - generateErrorCallback('Error while creating Offer', 'CreateOffer'), - - constraints - ); - }; - - /* - * Negotiates a WebRTC PeerConnection. - * - * Responsible for: - * * offer-answer exchange - * * iceCandidates - * * notification of remote streams being added/removed - * - */ - OT.PeerConnection = function(config) { - var _peerConnection, - _peerConnectionCompletionHandlers = [], - _iceProcessor = new IceCandidateProcessor(), - _offer, - _answer, - _state = 'new', - _messageDelegates = []; - - - OT.$.eventing(this); - - // if ice servers doesn't exist Firefox will throw an exception. Chrome - // interprets this as 'Use my default STUN servers' whereas FF reads it - // as 'Don't use STUN at all'. *Grumble* - if (!config.iceServers) config.iceServers = []; - - // Private methods - var delegateMessage = OT.$.bind(function(type, messagePayload) { - if (_messageDelegates.length) { - // We actually only ever send to the first delegate. This is because - // each delegate actually represents a Publisher/Subscriber that - // shares a single PeerConnection. If we sent to all delegates it - // would result in each message being processed multiple times by - // each PeerConnection. - _messageDelegates[0](type, messagePayload); - } - }, this), - - // Create and initialise the PeerConnection object. This deals with - // any differences between the various browser implementations and - // our own TBPlugin version. - // - // +completion+ is the function is call once we've either successfully - // created the PeerConnection or on failure. - // - // +localWebRtcStream+ will be null unless the callee is representing - // a publisher. This is an unfortunate implementation limitation - // of TBPlugin, it's not used for vanilla WebRTC. Hopefully this can - // be tidied up later. - // - createPeerConnection = OT.$.bind(function (completion, localWebRtcStream) { - if (_peerConnection) { - completion.call(null, null, _peerConnection); - return; - } - - _peerConnectionCompletionHandlers.push(completion); - - if (_peerConnectionCompletionHandlers.length > 1) { - // The PeerConnection is already being setup, just wait for - // it to be ready. - return; - } - - var pcConstraints = { - optional: [ - {DtlsSrtpKeyAgreement: true} - ] - }; - - OT.debug('Creating peer connection config "' + JSON.stringify(config) + '".'); - - if (!config.iceServers || config.iceServers.length === 0) { - // This should never happen unless something is misconfigured - OT.error('No ice servers present'); - } - - OT.$.createPeerConnection(config, pcConstraints, localWebRtcStream, - OT.$.bind(attachEventsToPeerConnection, this)); - }, this), - - // An auxiliary function to createPeerConnection. This binds the various event callbacks - // once the peer connection is created. - // - // +err+ will be non-null if an err occured while creating the PeerConnection - // +pc+ will be the PeerConnection object itself. - // - attachEventsToPeerConnection = OT.$.bind(function(err, pc) { - if (err) { - triggerError('Failed to create PeerConnection, exception: ' + - err.toString(), 'NewPeerConnection'); - - _peerConnectionCompletionHandlers = []; - return; - } - - OT.debug('OT attachEventsToPeerConnection'); - _peerConnection = pc; - - _peerConnection.onicecandidate = iceCandidateForwarder(delegateMessage); - _peerConnection.onaddstream = OT.$.bind(onRemoteStreamAdded, this); - _peerConnection.onremovestream = OT.$.bind(onRemoteStreamRemoved, this); - - if (_peerConnection.onsignalingstatechange !== undefined) { - _peerConnection.onsignalingstatechange = OT.$.bind(routeStateChanged, this); - } else if (_peerConnection.onstatechange !== undefined) { - _peerConnection.onstatechange = OT.$.bind(routeStateChanged, this); - } - - if (_peerConnection.oniceconnectionstatechange !== undefined) { - var failedStateTimer; - _peerConnection.oniceconnectionstatechange = function (event) { - if (event.target.iceConnectionState === 'failed') { - if (failedStateTimer) { - clearTimeout(failedStateTimer); - } - // We wait 5 seconds and make sure that it's still in the failed state - // before we trigger the error. This is because we sometimes see - // 'failed' and then 'connected' afterwards. - setTimeout(function () { - if (event.target.iceConnectionState === 'failed') { - triggerError('The stream was unable to connect due to a network error.' + - ' Make sure your connection isn\'t blocked by a firewall.', 'ICEWorkflow'); - } - }, 5000); - } - }; - } - - triggerPeerConnectionCompletion(null); - }, this), - - triggerPeerConnectionCompletion = function () { - while (_peerConnectionCompletionHandlers.length) { - _peerConnectionCompletionHandlers.shift().call(null); - } - }, - - // Clean up the Peer Connection and trigger the close event. - // This function can be called safely multiple times, it will - // only trigger the close event once (per PeerConnection object) - tearDownPeerConnection = function() { - // Our connection is dead, stop processing ICE candidates - if (_iceProcessor) _iceProcessor.setPeerConnection(null); - - qos.stopCollecting(); - - if (_peerConnection !== null) { - if (_peerConnection.destroy) { - // OTPlugin defines a destroy method on PCs. This allows - // the plugin to release any resources that it's holding. - _peerConnection.destroy(); - } - - _peerConnection = null; - this.trigger('close'); - } - }, - - routeStateChanged = function(event) { - var newState; - - if (typeof(event) === 'string') { - // The newest version of the API - newState = event; - - } else if (event.target && event.target.signalingState) { - // The slightly older version - newState = event.target.signalingState; - - } else { - // At least six months old version. Positively ancient, yeah? - newState = event.target.readyState; - } - - if (newState && newState.toLowerCase() !== _state) { - _state = newState.toLowerCase(); - OT.debug('PeerConnection.stateChange: ' + _state); - - switch(_state) { - case 'closed': - tearDownPeerConnection.call(this); - break; - } - } - }, - - qosCallback = OT.$.bind(function(parsedStats) { - this.trigger('qos', parsedStats); - }, this), - - getRemoteStreams = function() { - var streams; - - if (_peerConnection.getRemoteStreams) { - streams = _peerConnection.getRemoteStreams(); - } else if (_peerConnection.remoteStreams) { - streams = _peerConnection.remoteStreams; - } else { - throw new Error('Invalid Peer Connection object implements no ' + - 'method for retrieving remote streams'); - } - - // Force streams to be an Array, rather than a 'Sequence' object, - // which is browser dependent and does not behaviour like an Array - // in every case. - return Array.prototype.slice.call(streams); - }, - - /// PeerConnection signaling - onRemoteStreamAdded = function(event) { - this.trigger('streamAdded', event.stream); - }, - - onRemoteStreamRemoved = function(event) { - this.trigger('streamRemoved', event.stream); - }, - - // ICE Negotiation messages - - - // Relays a SDP payload (+sdp+), that is part of a message of type +messageType+ - // via the registered message delegators - relaySDP = function(messageType, sdp) { - delegateMessage(messageType, sdp); - }, - - - // Process an offer that - processOffer = function(message) { - var offer = new NativeRTCSessionDescription({type: 'offer', sdp: message.content.sdp}), - - // Relays +answer+ Answer - relayAnswer = function(answer) { - _iceProcessor.setPeerConnection(_peerConnection); - _iceProcessor.processPending(); - relaySDP(OT.Raptor.Actions.ANSWER, answer); - - qos.startCollecting(_peerConnection); - }, - - reportError = function(message, errorReason, prefix) { - triggerError('PeerConnection.offerProcessor ' + message + ': ' + - errorReason, prefix); - }; - - createPeerConnection(function() { - offerProcessor( - _peerConnection, - offer, - relayAnswer, - reportError - ); - }); - }, - - processAnswer = function(message) { - if (!message.content.sdp) { - OT.error('PeerConnection.processMessage: Weird answer message, no SDP.'); - return; - } - - _answer = new NativeRTCSessionDescription({type: 'answer', sdp: message.content.sdp}); - - _peerConnection.setRemoteDescription(_answer, - function () { - OT.debug('setRemoteDescription Success'); - }, function (errorReason) { - triggerError('Error while setting RemoteDescription ' + errorReason, - 'SetRemoteDescription'); - }); - - _iceProcessor.setPeerConnection(_peerConnection); - _iceProcessor.processPending(); - - qos.startCollecting(_peerConnection); - }, - - processSubscribe = function() { - OT.debug('PeerConnection.processSubscribe: Sending offer to subscriber.'); - - if (!_peerConnection) { - // TODO(rolly) I need to examine whether this can - // actually happen. If it does happen in the short - // term, I want it to be noisy. - throw new Error('PeerConnection broke!'); - } - - createPeerConnection(function() { - suscribeProcessor( - _peerConnection, - - // Success: Relay Offer - function(offer) { - _offer = offer; - relaySDP(OT.Raptor.Actions.OFFER, _offer); - }, - - // Failure - function(message, errorReason, prefix) { - triggerError('PeerConnection.suscribeProcessor ' + message + ': ' + - errorReason, prefix); - } - ); - }); - }, - - triggerError = OT.$.bind(function(errorReason, prefix) { - OT.error(errorReason); - this.trigger('error', errorReason, prefix); - }, this); - - this.addLocalStream = function(webRTCStream) { - createPeerConnection(function() { - _peerConnection.addStream(webRTCStream); - }, webRTCStream); - }; - - this.disconnect = function() { - _iceProcessor = null; - - if (_peerConnection) { - var currentState = (_peerConnection.signalingState || _peerConnection.readyState); - if (currentState && currentState.toLowerCase() !== 'closed') _peerConnection.close(); - - // In theory, calling close on the PeerConnection should trigger a statechange - // event with 'close'. For some reason I'm not seeing this in FF, hence we're - // calling it manually below - tearDownPeerConnection.call(this); - } - - this.off(); - }; - - this.processMessage = function(type, message) { - OT.debug('PeerConnection.processMessage: Received ' + - type + ' from ' + message.fromAddress); - - OT.debug(message); - - switch(type) { - case 'generateoffer': - processSubscribe.call(this, message); - break; - - case 'offer': - processOffer.call(this, message); - break; - - case 'answer': - case 'pranswer': - processAnswer.call(this, message); - break; - - case 'candidate': - _iceProcessor.process(message); - break; - - default: - OT.debug('PeerConnection.processMessage: Received an unexpected message of type ' + - type + ' from ' + message.fromAddress + ': ' + JSON.stringify(message)); - } - - return this; - }; - - this.setIceServers = function (iceServers) { - if (iceServers) { - config.iceServers = iceServers; - } - }; - - this.registerMessageDelegate = function(delegateFn) { - return _messageDelegates.push(delegateFn); - }; - - this.unregisterMessageDelegate = function(delegateFn) { - var index = OT.$.arrayIndexOf(_messageDelegates, delegateFn); - - if ( index !== -1 ) { - _messageDelegates.splice(index, 1); - } - return _messageDelegates.length; - }; - - this.remoteStreams = function() { - return _peerConnection ? getRemoteStreams() : []; - }; - - this.getStatsWithSingleParameter = function(callback) { - if (OT.$.hasCapabilities('getStatsWithSingleParameter')) { - createPeerConnection(function() { - _peerConnection.getStats(callback); - }); - } - }; - - var qos = new OT.PeerConnection.QOS(qosCallback); - }; - -})(window); -// -// There are three implementations of stats parsing in this file. -// 1. For Chrome: Chrome is currently using an older version of the API -// 2. For OTPlugin: The plugin is using a newer version of the API that -// exists in the latest WebRTC codebase -// 3. For Firefox: FF is using a version that looks a lot closer to the -// current spec. -// -// I've attempted to keep the three implementations from sharing any code, -// accordingly you'll notice a bunch of duplication between the three. -// -// This is acceptable as the goal is to be able to remove each implementation -// as it's no longer needed without any risk of affecting the others. If there -// was shared code between them then each removal would require an audit of -// all the others. -// -// !(function() { - /// - // Get Stats using the older API. Used by all current versions - // of Chrome. + var MAX_SIGNAL_DATA_LENGTH = 8192, + MAX_SIGNAL_TYPE_LENGTH = 128; + // - var parseStatsOldAPI = function parseStatsOldAPI (peerConnection, - prevStats, - currentStats, - completion) { - - /* this parses a result if there it contains the video bitrate */ - var parseAvgVideoBitrate = function (result) { - if (result.stat('googFrameHeightSent')) { - currentStats.videoBytesTransferred = result.stat('bytesSent'); - } else if (result.stat('googFrameHeightReceived')) { - currentStats.videoBytesTransferred = result.stat('bytesReceived'); - } else { - return NaN; - } - - var transferDelta = currentStats.videoBytesTransferred - - (prevStats.videoBytesTransferred || 0); - - return Math.round(transferDelta * 8 / currentStats.deltaSecs); - }, - - /* this parses a result if there it contains the audio bitrate */ - parseAvgAudioBitrate = function (result) { - if (result.stat('audioInputLevel')) { - currentStats.audioBytesTransferred = result.stat('bytesSent'); - } else if (result.stat('audioOutputLevel')) { - currentStats.audioBytesTransferred = result.stat('bytesReceived'); - } else { - return NaN; - } - - var transferDelta = currentStats.audioBytesTransferred - - (prevStats.audioBytesTransferred || 0); - return Math.round(transferDelta * 8 / currentStats.deltaSecs); - }, - - parseFrameRate = function (result) { - if (result.stat('googFrameRateSent')) { - return result.stat('googFrameRateSent'); - } else if (result.stat('googFrameRateReceived')) { - return result.stat('googFrameRateReceived'); - } - return null; - }, - - parseStatsReports = function (stats) { - if (stats.result) { - var resultList = stats.result(); - for (var resultIndex = 0; resultIndex < resultList.length; resultIndex++) { - var result = resultList[resultIndex]; - - if (result.stat) { - - if(result.stat('googActiveConnection') === 'true') { - currentStats.localCandidateType = result.stat('googLocalCandidateType'); - currentStats.remoteCandidateType = result.stat('googRemoteCandidateType'); - currentStats.transportType = result.stat('googTransportType'); - } - - var avgVideoBitrate = parseAvgVideoBitrate(result); - if (!isNaN(avgVideoBitrate)) { - currentStats.avgVideoBitrate = avgVideoBitrate; - } - - var avgAudioBitrate = parseAvgAudioBitrate(result); - if (!isNaN(avgAudioBitrate)) { - currentStats.avgAudioBitrate = avgAudioBitrate; - } - - var frameRate = parseFrameRate(result); - if (frameRate != null) { - currentStats.frameRate = frameRate; - } - } - } - } - - completion(null, currentStats); - }; - - peerConnection.getStats(parseStatsReports); - }; - - /// - // Get Stats for the OT Plugin, newer than Chromes version, but - // still not in sync with the spec. + // Error Codes: + // 413 - Type too long + // 400 - Type is invalid + // 413 - Data too long + // 400 - Data is invalid (can't be parsed as JSON) + // 429 - Rate limit exceeded + // 500 - Websocket connection is down + // 404 - To connection does not exist + // 400 - To is invalid // - var parseStatsOTPlugin = function parseStatsOTPlugin (peerConnection, - prevStats, - currentStats, - completion) { + OT.Signal = function(sessionId, fromConnectionId, options) { + var isInvalidType = function(type) { + // Our format matches the unreserved characters from the URI RFC: + // http://www.ietf.org/rfc/rfc3986 + return !/^[a-zA-Z0-9\-\._~]+$/.exec(type); + }, - var onStatsError = function onStatsError (error) { - completion(error); - }, - - /// - // From the Audio Tracks - // * avgAudioBitrate - // * audioBytesTransferred - // - parseAudioStats = function (statsReport) { - var lastBytesSent = prevStats.audioBytesTransferred || 0, - transferDelta; - - if (statsReport.audioInputLevel) { - currentStats.audioBytesTransferred = statsReport.bytesSent; - } - else if (statsReport.audioOutputLevel) { - currentStats.audioBytesTransferred = statsReport.bytesReceived; - } - - if (currentStats.audioBytesTransferred) { - transferDelta = currentStats.audioBytesTransferred - lastBytesSent; - currentStats.avgAudioBitrate = Math.round(transferDelta * 8 / currentStats.deltaSecs); - } - }, - - /// - // From the Video Tracks - // * frameRate - // * avgVideoBitrate - // * videoBytesTransferred - // - parseVideoStats = function (statsReport) { - - var lastBytesSent = prevStats.videoBytesTransferred || 0, - transferDelta; - - if (statsReport.googFrameHeightSent) { - currentStats.videoBytesTransferred = statsReport.bytesSent; - } - else if (statsReport.googFrameHeightReceived) { - currentStats.videoBytesTransferred = statsReport.bytesReceived; - } - - if (currentStats.videoBytesTransferred) { - transferDelta = currentStats.videoBytesTransferred - lastBytesSent; - currentStats.avgVideoBitrate = Math.round(transferDelta * 8 / currentStats.deltaSecs); - } - - if (statsReport.googFrameRateSent) { - currentStats.frameRate = statsReport.googFrameRateSent; - } else if (statsReport.googFrameRateReceived) { - currentStats.frameRate = statsReport.googFrameRateReceived; - } - }, - - isStatsForVideoTrack = function(statsReport) { - return statsReport.googFrameHeightSent !== void 0 || - statsReport.googFrameHeightReceived !== void 0 || - currentStats.videoBytesTransferred !== void 0 || - statsReport.googFrameRateSent !== void 0; - }, - - isStatsForIceCandidate = function(statsReport) { - return statsReport.googActiveConnection === 'true'; - }; - - peerConnection.getStats(null, function(statsReports) { - statsReports.forEach(function(statsReport) { - if (isStatsForIceCandidate(statsReport)) { - currentStats.localCandidateType = statsReport.googLocalCandidateType; - currentStats.remoteCandidateType = statsReport.googRemoteCandidateType; - currentStats.transportType = statsReport.googTransportType; + validateTo = function(toAddress) { + if (!toAddress) { + return { + code: 400, + reason: 'The signal to field was invalid. Either set it to a OT.Connection, ' + + 'OT.Session, or omit it entirely' + }; } - else if (isStatsForVideoTrack(statsReport)) { - parseVideoStats(statsReport); + + if ( !(toAddress instanceof OT.Connection || toAddress instanceof OT.Session) ) { + return { + code: 400, + reason: 'The To field was invalid' + }; + } + + return null; + }, + + validateType = function(type) { + var error = null; + + if (type === null || type === void 0) { + error = { + code: 400, + reason: 'The signal type was null or undefined. Either set it to a String value or ' + + 'omit it' + }; + } + else if (type.length > MAX_SIGNAL_TYPE_LENGTH) { + error = { + code: 413, + reason: 'The signal type was too long, the maximum length of it is ' + + MAX_SIGNAL_TYPE_LENGTH + ' characters' + }; + } + else if ( isInvalidType(type) ) { + error = { + code: 400, + reason: 'The signal type was invalid, it can only contain letters, ' + + 'numbers, \'-\', \'_\', and \'~\'.' + }; + } + + return error; + }, + + validateData = function(data) { + var error = null; + if (data === null || data === void 0) { + error = { + code: 400, + reason: 'The signal data was null or undefined. Either set it to a String value or ' + + 'omit it' + }; } else { - parseAudioStats(statsReport); - } - }); - - completion(null, currentStats); - }, onStatsError); - }; - - - /// - // Get Stats using the newer API. - // - var parseStatsNewAPI = function parseStatsNewAPI (peerConnection, - prevStats, - currentStats, - completion) { - - var onStatsError = function onStatsError (error) { - completion(error); - }, - - parseAvgVideoBitrate = function parseAvgVideoBitrate (result) { - if (result.bytesSent || result.bytesReceived) { - currentStats.videoBytesTransferred = result.bytesSent || result.bytesReceived; - } - else { - return NaN; - } - - var transferDelta = currentStats.videoBytesTransferred - - (prevStats.videoBytesTransferred || 0); - - return Math.round(transferDelta * 8 / currentStats.deltaSecs); - }, - - parseAvgAudioBitrate = function parseAvgAudioBitrate (result) { - if (result.bytesSent || result.bytesReceived) { - currentStats.audioBytesTransferred = result.bytesSent || result.bytesReceived; - } else { - return NaN; - } - - var transferDelta = currentStats.audioBytesTransferred - - (prevStats.audioBytesTransferred || 0); - return Math.round(transferDelta * 8 / currentStats.deltaSecs); - }; - - - peerConnection.getStats(null, function(stats) { - - for (var key in stats) { - if (stats.hasOwnProperty(key) && - (stats[key].type === 'outboundrtp' || stats[key].type === 'inboundrtp')) { - - var res = stats[key]; - - // Find the bandwidth info for video - if (res.id.indexOf('video') !== -1) { - var avgVideoBitrate = parseAvgVideoBitrate(res); - if(!isNaN(avgVideoBitrate)) { - currentStats.avgVideoBitrate = avgVideoBitrate; + try { + if (JSON.stringify(data).length > MAX_SIGNAL_DATA_LENGTH) { + error = { + code: 413, + reason: 'The data field was too long, the maximum size of it is ' + + MAX_SIGNAL_DATA_LENGTH + ' characters' + }; } - - } else if (res.id.indexOf('audio') !== -1) { - var avgAudioBitrate = parseAvgAudioBitrate(res); - if(!isNaN(avgAudioBitrate)) { - currentStats.avgAudioBitrate = avgAudioBitrate; - } - + } + catch(e) { + error = {code: 400, reason: 'The data field was not valid JSON'}; } } - } - completion(null, currentStats); - }, onStatsError); - }; - - - var parseQOS = function (peerConnection, prevStats, currentStats, completion) { - var firefoxVersion = window.navigator.userAgent - .toLowerCase().match(/Firefox\/([0-9\.]+)/i); - - if (TBPlugin.isInstalled()) { - parseQOS = parseStatsOTPlugin; - return parseStatsOTPlugin(peerConnection, prevStats, currentStats, completion); - } - else if (firefoxVersion !== null && parseFloat(firefoxVersion[1], 10) >= 27.0) { - parseQOS = parseStatsNewAPI; - return parseStatsNewAPI(peerConnection, prevStats, currentStats, completion); - } - else { - parseQOS = parseStatsOldAPI; - return parseStatsOldAPI(peerConnection, prevStats, currentStats, completion); - } - }; - - OT.PeerConnection.QOS = function (qosCallback) { - var _creationTime = OT.$.now(), - _peerConnection; - - var calculateQOS = OT.$.bind(function calculateQOS (prevStats) { - if (!_peerConnection) { - // We don't have a PeerConnection yet, or we did and - // it's been closed. Either way we're done. - return; - } - - var now = OT.$.now(); - - var currentStats = { - timeStamp: now, - duration: Math.round(now - _creationTime), - deltaSecs: (now - prevStats.timeStamp) / 1000 + return error; }; - var onParsedStats = function (err, parsedStats) { - if (err) { - OT.error('Failed to Parse QOS Stats: ' + JSON.stringify(err)); - return; - } - qosCallback(parsedStats, prevStats); + this.toRaptorMessage = function() { + var to = this.to; - // Recalculate the stats - setTimeout(OT.$.bind(calculateQOS, null, parsedStats), OT.PeerConnection.QOS.INTERVAL); - }; - - parseQOS(_peerConnection, prevStats, currentStats, onParsedStats); - }, this); - - - this.startCollecting = function (peerConnection) { - if (!peerConnection || !peerConnection.getStats) { - // It looks like this browser doesn't support getStats - // Bail. - return; + if (to && typeof(to) !== 'string') { + to = to.id; } - _peerConnection = peerConnection; - - calculateQOS({ - timeStamp: OT.$.now() - }); + return OT.Raptor.Message.signals.create(OT.APIKEY, sessionId, to, this.type, this.data); }; - this.stopCollecting = function () { - _peerConnection = null; + this.toHash = function() { + return options; }; - }; - // Recalculate the stats in 30 sec - OT.PeerConnection.QOS.INTERVAL = 30000; -})(); -!(function() { - var _peerConnections = {}; + this.error = null; - OT.PeerConnections = { - add: function(remoteConnection, streamId, config) { - var key = remoteConnection.id + '_' + streamId, - ref = _peerConnections[key]; - - if (!ref) { - ref = _peerConnections[key] = { - count: 0, - pc: new OT.PeerConnection(config) - }; + if (options) { + if (options.hasOwnProperty('data')) { + this.data = OT.$.clone(options.data); + this.error = validateData(this.data); } - // increase the PCs ref count by 1 - ref.count += 1; + if (options.hasOwnProperty('to')) { + this.to = options.to; - return ref.pc; - }, - - remove: function(remoteConnection, streamId) { - var key = remoteConnection.id + '_' + streamId, - ref = _peerConnections[key]; - - if (ref) { - ref.count -= 1; - - if (ref.count === 0) { - ref.pc.disconnect(); - delete _peerConnections[key]; + if (!this.error) { + this.error = validateTo(this.to); } } + + if (options.hasOwnProperty('type')) { + if (!this.error) { + this.error = validateType(options.type); + } + this.type = options.type; + } } + + this.valid = this.error === null; }; -})(window); -!(function() { +}(this)); - /* - * Abstracts PeerConnection related stuff away from OT.Publisher. - * - * Responsible for: - * * setting up the underlying PeerConnection (delegates to OT.PeerConnections) - * * triggering a connected event when the Peer connection is opened - * * triggering a disconnected event when the Peer connection is closed - * * providing a destroy method - * * providing a processMessage method - * - * Once the PeerConnection is connected and the video element playing it triggers - * the connected event - * - * Triggers the following events - * * connected - * * disconnected - */ - OT.PublisherPeerConnection = function(remoteConnection, session, streamId, webRTCStream) { - var _peerConnection, - _hasRelayCandidates = false, - _subscriberId = session._.subscriberMap[remoteConnection.id + '_' + streamId], - _onPeerClosed, - _onPeerError, - _relayMessageToPeer, - _onQOS; +// tb_require('../../../helpers/helpers.js') +// tb_require('../rumor/rumor.js') +// tb_require('./message.js') +// tb_require('./dispatch.js') +// tb_require('./signal.js') - // Private - _onPeerClosed = function() { - this.destroy(); - this.trigger('disconnected', this); - }; +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ - // Note: All Peer errors are fatal right now. - _onPeerError = function(errorReason, prefix) { - this.trigger('error', null, errorReason, this, prefix); - this.destroy(); - }; +function SignalError(code, message) { + this.code = code; + this.message = message; - _relayMessageToPeer = OT.$.bind(function(type, payload) { - if (!_hasRelayCandidates){ - var extractCandidates = type === OT.Raptor.Actions.CANDIDATE || - type === OT.Raptor.Actions.OFFER || - type === OT.Raptor.Actions.ANSWER || - type === OT.Raptor.Actions.PRANSWER ; + // Undocumented. Left in for backwards compatibility: + this.reason = message; +} - if (extractCandidates) { - var message = (type === OT.Raptor.Actions.CANDIDATE) ? payload.candidate : payload.sdp; - _hasRelayCandidates = message.indexOf('typ relay') !== -1; +// The Dispatcher bit is purely to make testing simpler, it defaults to a new OT.Raptor.Dispatcher +// so in normal operation you would omit it. +OT.Raptor.Socket = function(connectionId, widgetId, messagingSocketUrl, symphonyUrl, dispatcher) { + var _states = ['disconnected', 'connecting', 'connected', 'error', 'disconnecting'], + _sessionId, + _token, + _rumor, + _dispatcher, + _completion; + + + //// Private API + var setState = OT.$.statable(this, _states, 'disconnected'), + + onConnectComplete = function onConnectComplete(error) { + if (error) { + setState('error'); } - } - - switch(type) { - case OT.Raptor.Actions.ANSWER: - case OT.Raptor.Actions.PRANSWER: - if (session.sessionInfo.p2pEnabled) { - session._.jsepAnswerP2p(streamId, _subscriberId, payload.sdp); - } else { - session._.jsepAnswer(streamId, payload.sdp); - } - - break; - - case OT.Raptor.Actions.OFFER: - this.trigger('connected'); - - if (session.sessionInfo.p2pEnabled) { - session._.jsepOfferP2p(streamId, _subscriberId, payload.sdp); - - } else { - session._.jsepOffer(streamId, payload.sdp); - } - - break; - - case OT.Raptor.Actions.CANDIDATE: - if (session.sessionInfo.p2pEnabled) { - session._.jsepCandidateP2p(streamId, _subscriberId, payload); - - } else { - session._.jsepCandidate(streamId, payload); - } - } - }, this); - - _onQOS = OT.$.bind(function _onQOS (parsedStats, prevStats) { - this.trigger('qos', remoteConnection, parsedStats, prevStats); - }, this); - - OT.$.eventing(this); - - // Public - this.destroy = function() { - // Clean up our PeerConnection - if (_peerConnection) { - _peerConnection.off(); - OT.PeerConnections.remove(remoteConnection, streamId); - } - - _peerConnection = null; - }; - - this.processMessage = function(type, message) { - _peerConnection.processMessage(type, message); - }; - - // Init - this.init = function(iceServers) { - _peerConnection = OT.PeerConnections.add(remoteConnection, streamId, { - iceServers: iceServers - }); - - _peerConnection.on({ - close: _onPeerClosed, - error: _onPeerError, - qos: _onQOS - }, this); - - _peerConnection.registerMessageDelegate(_relayMessageToPeer); - _peerConnection.addLocalStream(webRTCStream); - - this.remoteConnection = function() { - return remoteConnection; - }; - - this.hasRelayCandidates = function() { - return _hasRelayCandidates; - }; - - }; - }; - -})(window); -!(function() { - - /* - * Abstracts PeerConnection related stuff away from OT.Subscriber. - * - * Responsible for: - * * setting up the underlying PeerConnection (delegates to OT.PeerConnections) - * * triggering a connected event when the Peer connection is opened - * * triggering a disconnected event when the Peer connection is closed - * * creating a video element when a stream is added - * * responding to stream removed intelligently - * * providing a destroy method - * * providing a processMessage method - * - * Once the PeerConnection is connected and the video element playing it - * triggers the connected event - * - * Triggers the following events - * * connected - * * disconnected - * * remoteStreamAdded - * * remoteStreamRemoved - * * error - * - */ - - OT.SubscriberPeerConnection = function(remoteConnection, session, stream, - subscriber, properties) { - var _peerConnection, - _destroyed = false, - _hasRelayCandidates = false, - _onPeerClosed, - _onRemoteStreamAdded, - _onRemoteStreamRemoved, - _onPeerError, - _relayMessageToPeer, - _setEnabledOnStreamTracksCurry, - _onQOS; - - // Private - _onPeerClosed = function() { - this.destroy(); - this.trigger('disconnected', this); - }; - - _onRemoteStreamAdded = function(remoteRTCStream) { - this.trigger('remoteStreamAdded', remoteRTCStream, this); - }; - - _onRemoteStreamRemoved = function(remoteRTCStream) { - this.trigger('remoteStreamRemoved', remoteRTCStream, this); - }; - - // Note: All Peer errors are fatal right now. - _onPeerError = function(errorReason, prefix) { - this.trigger('error', errorReason, this, prefix); - }; - - _relayMessageToPeer = OT.$.bind(function(type, payload) { - if (!_hasRelayCandidates){ - var extractCandidates = type === OT.Raptor.Actions.CANDIDATE || - type === OT.Raptor.Actions.OFFER || - type === OT.Raptor.Actions.ANSWER || - type === OT.Raptor.Actions.PRANSWER ; - - if (extractCandidates) { - var message = (type === OT.Raptor.Actions.CANDIDATE) ? payload.candidate : payload.sdp; - _hasRelayCandidates = message.indexOf('typ relay') !== -1; - } - } - - switch(type) { - case OT.Raptor.Actions.ANSWER: - case OT.Raptor.Actions.PRANSWER: - this.trigger('connected'); - - session._.jsepAnswerP2p(stream.id, subscriber.widgetId, payload.sdp); - break; - - case OT.Raptor.Actions.OFFER: - session._.jsepOfferP2p(stream.id, subscriber.widgetId, payload.sdp); - break; - - case OT.Raptor.Actions.CANDIDATE: - session._.jsepCandidateP2p(stream.id, subscriber.widgetId, payload); - break; - } - }, this); - - // Helper method used by subscribeToAudio/subscribeToVideo - _setEnabledOnStreamTracksCurry = function(isVideo) { - var method = 'get' + (isVideo ? 'Video' : 'Audio') + 'Tracks'; - - return function(enabled) { - var remoteStreams = _peerConnection.remoteStreams(), - tracks, - stream; - - if (remoteStreams.length === 0 || !remoteStreams[0][method]) { - // either there is no remote stream or we are in a browser that doesn't - // expose the media tracks (Firefox) - return; + else { + setState('connected'); } - for (var i=0, num=remoteStreams.length; i 0) { - OT.$.forEach(_peerConnection.remoteStreams(), _onRemoteStreamAdded, this); - } else if (numDelegates === 1) { - // We only bother with the PeerConnection negotiation if we don't already - // have a remote stream. - - var channelsToSubscribeTo; - - if (properties.subscribeToVideo || properties.subscribeToAudio) { - var audio = stream.getChannelsOfType('audio'), - video = stream.getChannelsOfType('video'); - - channelsToSubscribeTo = OT.$.map(audio, function(channel) { - return { - id: channel.id, - type: channel.type, - active: properties.subscribeToAudio - }; - }).concat(OT.$.map(video, function(channel) { - return { - id: channel.id, - type: channel.type, - active: properties.subscribeToVideo, - restrictFrameRate: properties.restrictFrameRate !== void 0 ? - properties.restrictFrameRate : false - }; - })); + if(err && err.code === 4001) { + reason = 'networkTimedout'; } - session._.subscriberCreate(stream, subscriber, channelsToSubscribeTo, - OT.$.bind(function(err, message) { - if (err) { - this.trigger('error', err.message, this, 'Subscribe'); - } - _peerConnection.setIceServers(OT.Raptor.parseIceServers(message)); - }, this)); - } - }; + setState('disconnected'); - this.getStatsWithSingleParameter = function(callback) { - if(_peerConnection) { - _peerConnection.getStatsWithSingleParameter(callback); - } - }; - }; + _dispatcher.onClose(reason); + }, this), -})(window); -!(function() { + onError = function onError () {}; + // @todo what does having an error mean? Are they always fatal? Are we disconnected now? -// Manages N Chrome elements - OT.Chrome = function(properties) { - var _visible = false, - _widgets = {}, - // Private helper function - _set = function(name, widget) { - widget.parent = this; - widget.appendTo(properties.parent); + //// Public API - _widgets[name] = widget; - - this[name] = widget; - }; - - if (!properties.parent) { - // @todo raise an exception + this.connect = function (token, sessionInfo, completion) { + if (!this.is('disconnected', 'error')) { + OT.warn('Cannot connect the Raptor Socket as it is currently connected. You should ' + + 'disconnect first.'); return; } - OT.$.eventing(this); + setState('connecting'); + _sessionId = sessionInfo.sessionId; + _token = token; + _completion = completion; - this.destroy = function() { - this.off(); - this.hide(); + var rumorChannel = '/v2/partner/' + OT.APIKEY + '/session/' + _sessionId; - for (var name in _widgets) { - _widgets[name].destroy(); + _rumor = new OT.Rumor.Socket(messagingSocketUrl, symphonyUrl); + _rumor.onClose(onClose); + _rumor.onMessage(OT.$.bind(_dispatcher.dispatch, _dispatcher)); + + _rumor.connect(connectionId, OT.$.bind(function(error) { + if (error) { + onConnectComplete({ + reason: 'WebSocketConnection', + code: error.code, + message: error.message + }); + return; } - }; - this.show = function() { - _visible = true; + // we do this here to avoid getting connect errors twice + _rumor.onError(onError); - for (var name in _widgets) { - _widgets[name].show(); - } - }; + OT.debug('Raptor Socket connected. Subscribing to ' + + rumorChannel + ' on ' + messagingSocketUrl); - this.hide = function() { - _visible = false; + _rumor.subscribe([rumorChannel]); - for (var name in _widgets) { - _widgets[name].hide(); - } - }; + //connect to session + var connectMessage = OT.Raptor.Message.connections.create(OT.APIKEY, + _sessionId, _rumor.id()); + this.publish(connectMessage, {'X-TB-TOKEN-AUTH': _token}, OT.$.bind(function(error) { + if (error) { + error.message = 'ConnectToSession:' + error.code + + ':Received error response to connection create message.'; + var payload = { + reason: 'ConnectToSession', + code: error.code, + message: 'Received error response to connection create message.' + }; + var event = { + action: 'Connect', + variation: 'Failure', + payload: payload, + sessionId: _sessionId, + partnerId: OT.APIKEY, + connectionId: connectionId + }; + OT.analytics.logEvent(event); + onConnectComplete(payload); + return; + } - - // Adds the widget to the chrome and to the DOM. Also creates a accessor - // property for it on the chrome. - // - // @example - // chrome.set('foo', new FooWidget()); - // chrome.foo.setDisplayMode('on'); - // - // @example - // chrome.set({ - // foo: new FooWidget(), - // bar: new BarWidget() - // }); - // chrome.foo.setDisplayMode('on'); - // - this.set = function(widgetName, widget) { - if (typeof(widgetName) === 'string' && widget) { - _set.call(this, widgetName, widget); - - } else { - for (var name in widgetName) { - if (widgetName.hasOwnProperty(name)) { - _set.call(this, name, widgetName[name]); + this.publish( OT.Raptor.Message.sessions.get(OT.APIKEY, _sessionId), + function (error) { + if (error) { + var payload = { + reason: 'GetSessionState', + code: error.code, + message: 'Received error response to session read' + }; + var event = { + action: 'Connect', + variation: 'Failure', + payload: payload, + sessionId: _sessionId, + partnerId: OT.APIKEY, + connectionId: connectionId + }; + OT.analytics.logEvent(event); + onConnectComplete(payload); + } else { + onConnectComplete.apply(null, arguments); } - } - } - return this; - }; - + }); + }, this)); + }, this)); }; -})(window); -!(function() { - if (!OT.Chrome.Behaviour) OT.Chrome.Behaviour = {}; + this.disconnect = function (drainSocketBuffer) { + if (this.is('disconnected')) return; - // A mixin to encapsulate the basic widget behaviour. This needs a better name, - // it's not actually a widget. It's actually "Behaviour that can be applied to - // an object to make it support the basic Chrome widget workflow"...but that would - // probably been too long a name. - OT.Chrome.Behaviour.Widget = function(widget, options) { - var _options = options || {}, - _mode, - _previousMode; - - // - // @param [String] mode - // 'on', 'off', or 'auto' - // - widget.setDisplayMode = function(mode) { - var newMode = mode || 'auto'; - if (_mode === newMode) return; - - OT.$.removeClass(this.domElement, 'OT_mode-' + _mode); - OT.$.addClass(this.domElement, 'OT_mode-' + newMode); - - _previousMode = _mode; - _mode = newMode; - }; - - widget.show = function() { - this.setDisplayMode(_previousMode); - if (_options.onShow) _options.onShow(); - - return this; - }; - - widget.hide = function() { - this.setDisplayMode('off'); - if (_options.onHide) _options.onHide(); - - return this; - }; - - widget.destroy = function() { - if (_options.onDestroy) _options.onDestroy(this.domElement); - if (this.domElement) OT.$.removeElement(this.domElement); - - return widget; - }; - - widget.appendTo = function(parent) { - // create the element under parent - this.domElement = OT.$.createElement(_options.nodeName || 'div', - _options.htmlAttributes, - _options.htmlContent); - - if (_options.onCreate) _options.onCreate(this.domElement); - - widget.setDisplayMode(_options.mode); - - if (_options.mode === 'auto') { - // if the mode is auto we hold the "on mode" for 2 seconds - // this will let the proper widgets nicely fade away and help discoverability - OT.$.addClass(widget.domElement, 'OT_mode-on-hold'); - setTimeout(function() { - OT.$.removeClass(widget.domElement, 'OT_mode-on-hold'); - }, 2000); - } - - - // add the widget to the parent - parent.appendChild(this.domElement); - - return widget; - }; + setState('disconnecting'); + _rumor.disconnect(drainSocketBuffer); }; -})(window); -!(function() { - - // BackingBar Chrome Widget + // Publishs +message+ to the Symphony app server. // - // nameMode (String) - // Whether or not the name panel is being displayed - // Possible values are: "auto" (the name is displayed - // when the stream is first displayed and when the user mouses over the display), - // "off" (the name is not displayed), and "on" (the name is displayed). + // The completion handler is optional, as is the headers + // dict, but if you provide the completion handler it must + // be the last argument. // - // muteMode (String) - // Whether or not the mute button is being displayed - // Possible values are: "auto" (the mute button is displayed - // when the stream is first displayed and when the user mouses over the display), - // "off" (the mute button is not displayed), and "on" (the mute button is displayed). - // - // displays a backing bar - // can be shown/hidden - // can be destroyed - OT.Chrome.BackingBar = function(options) { - var _nameMode = options.nameMode, - _muteMode = options.muteMode; - - function getDisplayMode() { - if(_nameMode === 'on' || _muteMode === 'on') { - return 'on'; - } else if(_nameMode === 'mini' || _muteMode === 'mini') { - return 'mini'; - } else if(_nameMode === 'mini-auto' || _muteMode === 'mini-auto') { - return 'mini-auto'; - } else if(_nameMode === 'auto' || _muteMode === 'auto') { - return 'auto'; - } else { - return 'off'; - } + this.publish = function (message, headers, completion) { + if (_rumor.isNot('connected')) { + OT.error('OT.Raptor.Socket: cannot publish until the socket is connected.' + message); + return; } - // Mixin common widget behaviour - OT.Chrome.Behaviour.Widget(this, { - mode: getDisplayMode(), - nodeName: 'div', - htmlContent: '', - htmlAttributes: { - className: 'OT_bar OT_edge-bar-item' + var transactionId = OT.$.uuid(), + _headers = {}, + _completion; + + // Work out if which of the optional arguments (headers, completion) + // have been provided. + if (headers) { + if (OT.$.isFunction(headers)) { + _headers = {}; + _completion = headers; } - }); + else { + _headers = headers; + } + } + if (!_completion && completion && OT.$.isFunction(completion)) _completion = completion; - this.setNameMode = function(nameMode) { - _nameMode = nameMode; - this.setDisplayMode(getDisplayMode()); - }; - this.setMuteMode = function(muteMode) { - _muteMode = muteMode; - this.setDisplayMode(getDisplayMode()); - }; + if (_completion) _dispatcher.registerCallback(transactionId, _completion); + OT.debug('OT.Raptor.Socket Publish (ID:' + transactionId + ') '); + OT.debug(message); + + _rumor.publish([symphonyUrl], message, OT.$.extend(_headers, { + 'Content-Type': 'application/x-raptor+v2', + 'TRANSACTION-ID': transactionId, + 'X-TB-FROM-ADDRESS': _rumor.id() + })); + + return transactionId; }; -})(window); -!(function() { + // Register a new stream against _sessionId + this.streamCreate = function(name, audioFallbackEnabled, channels, minBitrate, maxBitrate, + completion) { + var streamId = OT.$.uuid(), + message = OT.Raptor.Message.streams.create( OT.APIKEY, + _sessionId, + streamId, + name, + audioFallbackEnabled, + channels, + minBitrate, + maxBitrate); - // NamePanel Chrome Widget - // - // mode (String) - // Whether to display the name. Possible values are: "auto" (the name is displayed - // when the stream is first displayed and when the user mouses over the display), - // "off" (the name is not displayed), and "on" (the name is displayed). - // - // displays a name - // can be shown/hidden - // can be destroyed - OT.Chrome.NamePanel = function(options) { - var _name = options.name; + this.publish(message, function(error, message) { + completion(error, streamId, message); + }); + }; - if (!_name || OT.$.trim(_name).length === '') { - _name = null; + this.streamDestroy = function(streamId) { + this.publish( OT.Raptor.Message.streams.destroy(OT.APIKEY, _sessionId, streamId) ); + }; - // THere's no name, just flip the mode off - options.mode = 'off'; + this.streamChannelUpdate = function(streamId, channelId, attributes) { + this.publish( OT.Raptor.Message.streamChannels.update(OT.APIKEY, _sessionId, + streamId, channelId, attributes) ); + }; + + this.subscriberCreate = function(streamId, subscriberId, channelsToSubscribeTo, completion) { + this.publish( OT.Raptor.Message.subscribers.create(OT.APIKEY, _sessionId, + streamId, subscriberId, _rumor.id(), channelsToSubscribeTo), completion ); + }; + + this.subscriberDestroy = function(streamId, subscriberId) { + this.publish( OT.Raptor.Message.subscribers.destroy(OT.APIKEY, _sessionId, + streamId, subscriberId) ); + }; + + this.subscriberUpdate = function(streamId, subscriberId, attributes) { + this.publish( OT.Raptor.Message.subscribers.update(OT.APIKEY, _sessionId, + streamId, subscriberId, attributes) ); + }; + + this.subscriberChannelUpdate = function(streamId, subscriberId, channelId, attributes) { + this.publish( OT.Raptor.Message.subscriberChannels.update(OT.APIKEY, _sessionId, + streamId, subscriberId, channelId, attributes) ); + }; + + this.forceDisconnect = function(connectionIdToDisconnect, completion) { + this.publish( OT.Raptor.Message.connections.destroy(OT.APIKEY, _sessionId, + connectionIdToDisconnect), completion ); + }; + + this.forceUnpublish = function(streamIdToUnpublish, completion) { + this.publish( OT.Raptor.Message.streams.destroy(OT.APIKEY, _sessionId, + streamIdToUnpublish), completion ); + }; + + this.jsepCandidate = function(streamId, candidate) { + this.publish( + OT.Raptor.Message.streams.candidate(OT.APIKEY, _sessionId, streamId, candidate) + ); + }; + + this.jsepCandidateP2p = function(streamId, subscriberId, candidate) { + this.publish( + OT.Raptor.Message.subscribers.candidate(OT.APIKEY, _sessionId, streamId, + subscriberId, candidate) + ); + }; + + this.jsepOffer = function(uri, offerSdp) { + this.publish( OT.Raptor.Message.offer(uri, offerSdp) ); + }; + + this.jsepAnswer = function(streamId, answerSdp) { + this.publish( OT.Raptor.Message.streams.answer(OT.APIKEY, _sessionId, streamId, answerSdp) ); + }; + + this.jsepAnswerP2p = function(streamId, subscriberId, answerSdp) { + this.publish( OT.Raptor.Message.subscribers.answer(OT.APIKEY, _sessionId, streamId, + subscriberId, answerSdp) ); + }; + + this.signal = function(options, completion, logEventFn) { + var signal = new OT.Signal(_sessionId, _rumor.id(), options || {}); + + if (!signal.valid) { + if (completion && OT.$.isFunction(completion)) { + completion( new SignalError(signal.error.code, signal.error.reason), signal.toHash() ); + } + + return; } - this.setName = OT.$.bind(function(name) { - if (!_name) this.setDisplayMode('auto'); - _name = name; - this.domElement.innerHTML = _name; - }); - - // Mixin common widget behaviour - OT.Chrome.Behaviour.Widget(this, { - mode: options.mode, - nodeName: 'h1', - htmlContent: _name, - htmlAttributes: { - className: 'OT_name OT_edge-bar-item' - } - }); - - }; - -})(window); -!(function() { - - OT.Chrome.MuteButton = function(options) { - var _onClickCb, - _muted = options.muted || false, - updateClasses, - attachEvents, - detachEvents, - onClick; - - updateClasses = OT.$.bind(function() { - if (_muted) { - OT.$.addClass(this.domElement, 'OT_active'); + this.publish( signal.toRaptorMessage(), function(err) { + var error; + if (err) { + error = new SignalError(err.code, err.message); } else { - OT.$.removeClass(this.domElement, 'OT_active '); - } - }, this); - - // Private Event Callbacks - attachEvents = function(elem) { - _onClickCb = OT.$.bind(onClick, this); - OT.$.on(elem, 'click', _onClickCb); - }; - - detachEvents = function(elem) { - _onClickCb = null; - OT.$.off(elem, 'click', _onClickCb); - }; - - onClick = function() { - _muted = !_muted; - - updateClasses(); - - if (_muted) { - this.parent.trigger('muted', this); - } else { - this.parent.trigger('unmuted', this); + var typeStr = signal.data? typeof(signal.data) : null; + logEventFn('signal', 'send', {type: typeStr}); } - return false; - }; - - OT.$.defineProperties(this, { - muted: { - get: function() { return _muted; }, - set: function(muted) { - _muted = muted; - updateClasses(); - } - } - }); - - // Mixin common widget behaviour - var classNames = _muted ? 'OT_edge-bar-item OT_mute OT_active' : 'OT_edge-bar-item OT_mute'; - OT.Chrome.Behaviour.Widget(this, { - mode: options.mode, - nodeName: 'button', - htmlContent: 'Mute', - htmlAttributes: { - className: classNames - }, - onCreate: OT.$.bind(attachEvents, this), - onDestroy: OT.$.bind(detachEvents, this) + if (completion && OT.$.isFunction(completion)) completion(error, signal.toHash()); }); }; - -})(window); -!(function() { - - // Archving Chrome Widget - // - // mode (String) - // Whether to display the archving widget. Possible values are: "on" (the status is displayed - // when archiving and briefly when archving ends) and "off" (the status is not displayed) - - // Whether to display the archving widget. Possible values are: "auto" (the name is displayed - // when the status is first displayed and when the user mouses over the display), - // "off" (the name is not displayed), and "on" (the name is displayed). - // - // displays a name - // can be shown/hidden - // can be destroyed - OT.Chrome.Archiving = function(options) { - var _archiving = options.archiving, - _archivingStarted = options.archivingStarted || 'Archiving on', - _archivingEnded = options.archivingEnded || 'Archiving off', - _initialState = true, - _lightBox, - _light, - _text, - _textNode, - renderStageDelayedAction, - renderText, - renderStage; - - renderText = function(text) { - _textNode.nodeValue = text; - _lightBox.setAttribute('title', text); - }; - - renderStage = OT.$.bind(function() { - if(renderStageDelayedAction) { - clearTimeout(renderStageDelayedAction); - renderStageDelayedAction = null; - } - - if(_archiving) { - OT.$.addClass(_light, 'OT_active'); - } else { - OT.$.removeClass(_light, 'OT_active'); - } - - OT.$.removeClass(this.domElement, 'OT_archiving-' + (!_archiving ? 'on' : 'off')); - OT.$.addClass(this.domElement, 'OT_archiving-' + (_archiving ? 'on' : 'off')); - if(options.show && _archiving) { - renderText(_archivingStarted); - OT.$.addClass(_text, 'OT_mode-on'); - OT.$.removeClass(_text, 'OT_mode-auto'); - this.setDisplayMode('on'); - renderStageDelayedAction = setTimeout(function() { - OT.$.addClass(_text, 'OT_mode-auto'); - OT.$.removeClass(_text, 'OT_mode-on'); - }, 5000); - } else if(options.show && !_initialState) { - OT.$.addClass(_text, 'OT_mode-on'); - OT.$.removeClass(_text, 'OT_mode-auto'); - this.setDisplayMode('on'); - renderText(_archivingEnded); - renderStageDelayedAction = setTimeout(OT.$.bind(function() { - this.setDisplayMode('off'); - }, this), 5000); - } else { - this.setDisplayMode('off'); - } - }, this); - - // Mixin common widget behaviour - OT.Chrome.Behaviour.Widget(this, { - mode: _archiving && options.show && 'on' || 'off', - nodeName: 'h1', - htmlAttributes: {className: 'OT_archiving OT_edge-bar-item OT_edge-bottom'}, - onCreate: OT.$.bind(function() { - _lightBox = OT.$.createElement('div', { - className: 'OT_archiving-light-box' - }, ''); - _light = OT.$.createElement('div', { - className: 'OT_archiving-light' - }, ''); - _lightBox.appendChild(_light); - _text = OT.$.createElement('div', { - className: 'OT_archiving-status OT_mode-on OT_edge-bar-item OT_edge-bottom' - }, ''); - _textNode = document.createTextNode(''); - _text.appendChild(_textNode); - this.domElement.appendChild(_lightBox); - this.domElement.appendChild(_text); - renderStage(); - }, this) - }); - - this.setShowArchiveStatus = OT.$.bind(function(show) { - options.show = show; - if(this.domElement) { - renderStage.call(this); - } - }, this); - - this.setArchiving = OT.$.bind(function(status) { - _archiving = status; - _initialState = false; - if(this.domElement) { - renderStage.call(this); - } - }, this); - + this.id = function() { + return _rumor && _rumor.id(); }; -})(window); -!(function() { + if(dispatcher == null) { + dispatcher = new OT.Raptor.Dispatcher(); + } + _dispatcher = dispatcher; +}; - OT.Chrome.AudioLevelMeter = function(options) { +// tb_require('../helpers/helpers.js') +// tb_require('../helpers/lib/capabilities.js') +// tb_require('./peer_connection/publisher_peer_connection.js') +// tb_require('./peer_connection/subscriber_peer_connection.js') - var widget = this, - _meterBarElement, - _voiceOnlyIconElement, - _meterValueElement, - _value, - _maxValue = options.maxValue || 1, - _minValue = options.minValue || 0; - - function onCreate() { - _meterBarElement = OT.$.createElement('div', { - className: 'OT_audio-level-meter__bar' - }, ''); - _meterValueElement = OT.$.createElement('div', { - className: 'OT_audio-level-meter__value' - }, ''); - _voiceOnlyIconElement = OT.$.createElement('div', { - className: 'OT_audio-level-meter__audio-only-img' - }, ''); - - widget.domElement.appendChild(_meterBarElement); - widget.domElement.appendChild(_voiceOnlyIconElement); - widget.domElement.appendChild(_meterValueElement); - } - - function updateView() { - var percentSize = _value * 100 / (_maxValue - _minValue); - _meterValueElement.style.width = _meterValueElement.style.height = 2 * percentSize + '%'; - _meterValueElement.style.top = _meterValueElement.style.right = -percentSize + '%'; - } - - // Mixin common widget behaviour - var widgetOptions = { - mode: options ? options.mode : 'auto', - nodeName: 'div', - htmlAttributes: { - className: 'OT_audio-level-meter' - }, - onCreate: onCreate - }; - - OT.Chrome.Behaviour.Widget(this, widgetOptions); - - // override - var _setDisplayMode = OT.$.bind(widget.setDisplayMode, widget); - widget.setDisplayMode = function(mode) { - _setDisplayMode(mode); - if (mode === 'off') { - if (options.onPassivate) options.onPassivate(); - } else { - if (options.onActivate) options.onActivate(); - } - }; - - widget.setValue = function(value) { - _value = value; - updateView(); - }; - }; - -})(window); -!(function() { - OT.Chrome.VideoDisabledIndicator = function(options) { - var _mode, - _videoDisabled = false, - _warning = false, - updateClasses; - - _mode = options.mode || 'auto'; - updateClasses = function(domElement) { - if (_videoDisabled) { - OT.$.addClass(domElement, 'OT_video-disabled'); - } else { - OT.$.removeClass(domElement, 'OT_video-disabled'); - } - if(_warning) { - OT.$.addClass(domElement, 'OT_video-disabled-warning'); - } else { - OT.$.removeClass(domElement, 'OT_video-disabled-warning'); - } - if ((_videoDisabled || _warning) && (_mode === 'auto' || _mode === 'on')) { - OT.$.addClass(domElement, 'OT_active'); - } else { - OT.$.removeClass(domElement, 'OT_active'); - } - }; - - this.disableVideo = function(value) { - _videoDisabled = value; - if(value === true) { - _warning = false; - } - updateClasses(this.domElement); - }; - - this.setWarning = function(value) { - _warning = value; - updateClasses(this.domElement); - }; - - // Mixin common widget behaviour - OT.Chrome.Behaviour.Widget(this, { - mode: _mode, - nodeName: 'div', - htmlAttributes: { - className: 'OT_video-disabled-indicator' - } - }); - - this.setDisplayMode = function(mode) { - _mode = mode; - updateClasses(this.domElement); - }; - }; -})(window); -(function() { -/* Stylable Notes - * RTC doesn't need to wait until anything is loaded - * Some bits are controlled by multiple flags, i.e. buttonDisplayMode and nameDisplayMode. - * When there are multiple flags how is the final setting chosen? - * When some style bits are set updates will need to be pushed through to the Chrome - */ - - // Mixes the StylableComponent behaviour into the +self+ object. It will - // also set the default styles to +initialStyles+. - // - // @note This Mixin is dependent on OT.Eventing. - // - // - // @example - // - // function SomeObject { - // OT.StylableComponent(this, { - // name: 'SomeObject', - // foo: 'bar' - // }); - // } - // - // var obj = new SomeObject(); - // obj.getStyle('foo'); // => 'bar' - // obj.setStyle('foo', 'baz') - // obj.getStyle('foo'); // => 'baz' - // obj.getStyle(); // => {name: 'SomeObject', foo: 'baz'} - // - OT.StylableComponent = function(self, initalStyles) { - if (!self.trigger) { - throw new Error('OT.StylableComponent is dependent on the mixin OT.$.eventing. ' + - 'Ensure that this is included in the object before StylableComponent.'); - } - - // Broadcast style changes as the styleValueChanged event - var onStyleChange = function(key, value, oldValue) { - if (oldValue) { - self.trigger('styleValueChanged', key, value, oldValue); - } else { - self.trigger('styleValueChanged', key, value); - } - }; - - var _style = new Style(initalStyles, onStyleChange); - - /** - * Returns an object that has the properties that define the current user interface controls of - * the Publisher. You can modify the properties of this object and pass the object to the - * setStyle() method of thePublisher object. (See the documentation for - * setStyle() to see the styles that define this object.) - * @return {Object} The object that defines the styles of the Publisher. - * @see setStyle() - * @method #getStyle - * @memberOf Publisher - */ - - /** - * Returns an object that has the properties that define the current user interface controls of - * the Subscriber. You can modify the properties of this object and pass the object to the - * setStyle() method of the Subscriber object. (See the documentation for - * setStyle() to see the styles that define this object.) - * @return {Object} The object that defines the styles of the Subscriber. - * @see setStyle() - * @method #getStyle - * @memberOf Subscriber - */ - // If +key+ is falsly then all styles will be returned. - self.getStyle = function(key) { - return _style.get(key); - }; - - /** - * Sets properties that define the appearance of some user interface controls of the Publisher. - * - *

You can either pass one parameter or two parameters to this method.

- * - *

If you pass one parameter, style, it is an object that has the following - * properties: - * - *

    - *
  • audioLevelDisplayMode (String) — How to display the audio level - * indicator. Possible values are: "auto" (the indicator is displayed when the - * video is disabled), "off" (the indicator is not displayed), and - * "on" (the indicator is always displayed).
  • - * - *
  • backgroundImageURI (String) — A URI for an image to display as - * the background image when a video is not displayed. (A video may not be displayed if - * you call publishVideo(false) on the Publisher object). You can pass an http - * or https URI to a PNG, JPEG, or non-animated GIF file location. You can also use the - * data URI scheme (instead of http or https) and pass in base-64-encrypted - * PNG data, such as that obtained from the - * Publisher.getImgData() method. For example, - * you could set the property to "data:VBORw0KGgoAA...", where the portion of - * the string after "data:" is the result of a call to - * Publisher.getImgData(). If the URL or the image data is invalid, the - * property is ignored (the attempt to set the image fails silently). - *

    - * Note that in Internet Explorer 8 (using the OpenTok Plugin for Internet Explorer), - * you cannot set the backgroundImageURI style to a string larger than - * 32 kB. This is due to an IE 8 limitation on the size of URI strings. Due to this - * limitation, you cannot set the backgroundImageURI style to a string obtained - * with the getImgData() method. - *

  • - * - *
  • buttonDisplayMode (String) — How to display the microphone - * controls. Possible values are: "auto" (controls are displayed when the - * stream is first displayed and when the user mouses over the display), "off" - * (controls are not displayed), and "on" (controls are always displayed).
  • - * - *
  • nameDisplayMode (String) — Whether to display the stream name. - * Possible values are: "auto" (the name is displayed when the stream is first - * displayed and when the user mouses over the display), "off" (the name is not - * displayed), and "on" (the name is always displayed).
  • - *
- *

- * - *

For example, the following code passes one parameter to the method:

- * - *
myPublisher.setStyle({nameDisplayMode: "off"});
- * - *

If you pass two parameters, style and value, they are - * key-value pair that define one property of the display style. For example, the following - * code passes two parameter values to the method:

- * - *
myPublisher.setStyle("nameDisplayMode", "off");
- * - *

You can set the initial settings when you call the Session.publish() - * or OT.initPublisher() method. Pass a style property as part of the - * properties parameter of the method.

- * - *

The OT object dispatches an exception event if you pass in an invalid style - * to the method. The code property of the ExceptionEvent object is set to 1011.

- * - * @param {Object} style Either an object containing properties that define the style, or a - * String defining this single style property to set. - * @param {String} value The value to set for the style passed in. Pass a value - * for this parameter only if the value of the style parameter is a String.

- * - * @see getStyle() - * @return {Publisher} The Publisher object - * @see setStyle() - * - * @see Session.publish() - * @see OT.initPublisher() - * @method #setStyle - * @memberOf Publisher - */ - - /** - * Sets properties that define the appearance of some user interface controls of the Subscriber. - * - *

You can either pass one parameter or two parameters to this method.

- * - *

If you pass one parameter, style, it is an object that has the following - * properties: - * - *

    - *
  • audioLevelDisplayMode (String) — How to display the audio level - * indicator. Possible values are: "auto" (the indicator is displayed when the - * video is disabled), "off" (the indicator is not displayed), and - * "on" (the indicator is always displayed).
  • - * - *
  • backgroundImageURI (String) — A URI for an image to display as - * the background image when a video is not displayed. (A video may not be displayed if - * you call subscribeToVideo(false) on the Publisher object). You can pass an - * http or https URI to a PNG, JPEG, or non-animated GIF file location. You can also use the - * data URI scheme (instead of http or https) and pass in base-64-encrypted - * PNG data, such as that obtained from the - * Subscriber.getImgData() method. For example, - * you could set the property to "data:VBORw0KGgoAA...", where the portion of - * the string after "data:" is the result of a call to - * Publisher.getImgData(). If the URL or the image data is invalid, the - * property is ignored (the attempt to set the image fails silently). - *

    - * Note that in Internet Explorer 8 (using the OpenTok Plugin for Internet Explorer), - * you cannot set the backgroundImageURI style to a string larger than - * 32 kB. This is due to an IE 8 limitation on the size of URI strings. Due to this - * limitation, you cannot set the backgroundImageURI style to a string obtained - * with the getImgData() method. - *

  • - * - *
  • buttonDisplayMode (String) — How to display the speaker - * controls. Possible values are: "auto" (controls are displayed when the - * stream is first displayed and when the user mouses over the display), "off" - * (controls are not displayed), and "on" (controls are always displayed).
  • - * - *
  • nameDisplayMode (String) — Whether to display the stream name. - * Possible values are: "auto" (the name is displayed when the stream is first - * displayed and when the user mouses over the display), "off" (the name is not - * displayed), and "on" (the name is always displayed).
  • - * - *
  • videoDisabledDisplayMode (String) — Whether to display the video - * disabled indicator and video disabled warning icons for a Subscriber. These icons - * indicate that the video has been disabled (or is in risk of being disabled for - * the warning icon) due to poor stream quality. Possible values are: "auto" - * (the icons are automatically when the displayed video is disabled or in risk of being - * disabled due to poor stream quality), "off" (do not display the icons), and - * "on" (display the icons).
  • - *
- *

- * - *

For example, the following code passes one parameter to the method:

- * - *
mySubscriber.setStyle({nameDisplayMode: "off"});
- * - *

If you pass two parameters, style and value, they are key-value - * pair that define one property of the display style. For example, the following code passes - * two parameter values to the method:

- * - *
mySubscriber.setStyle("nameDisplayMode", "off");
- * - *

You can set the initial settings when you call the Session.subscribe() method. - * Pass a style property as part of the properties parameter of the - * method.

- * - *

The OT object dispatches an exception event if you pass in an invalid style - * to the method. The code property of the ExceptionEvent object is set to 1011.

- * - * @param {Object} style Either an object containing properties that define the style, or a - * String defining this single style property to set. - * @param {String} value The value to set for the style passed in. Pass a value - * for this parameter only if the value of the style parameter is a String.

- * - * @returns {Subscriber} The Subscriber object. - * - * @see getStyle() - * @see setStyle() - * - * @see Session.subscribe() - * @method #setStyle - * @memberOf Subscriber - */ - self.setStyle = function(keyOrStyleHash, value, silent) { - if (typeof(keyOrStyleHash) !== 'string') { - _style.setAll(keyOrStyleHash, silent); - } else { - _style.set(keyOrStyleHash, value); - } - return this; - }; - }; - - var Style = function(initalStyles, onStyleChange) { - var _style = {}, - _COMPONENT_STYLES, - _validStyleValues, - isValidStyle, - castValue; - - _COMPONENT_STYLES = [ - 'showMicButton', - 'showSpeakerButton', - 'nameDisplayMode', - 'buttonDisplayMode', - 'backgroundImageURI' - ]; - - _validStyleValues = { - buttonDisplayMode: ['auto', 'mini', 'mini-auto', 'off', 'on'], - nameDisplayMode: ['auto', 'off', 'on'], - audioLevelDisplayMode: ['auto', 'off', 'on'], - showSettingsButton: [true, false], - showMicButton: [true, false], - backgroundImageURI: null, - showControlBar: [true, false], - showArchiveStatus: [true, false], - videoDisabledDisplayMode: ['auto', 'off', 'on'] - }; - - - // Validates the style +key+ and also whether +value+ is valid for +key+ - isValidStyle = function(key, value) { - return key === 'backgroundImageURI' || - (_validStyleValues.hasOwnProperty(key) && - OT.$.arrayIndexOf(_validStyleValues[key], value) !== -1 ); - }; - - castValue = function(value) { - switch(value) { - case 'true': - return true; - case 'false': - return false; - default: - return value; - } - }; - - // Returns a shallow copy of the styles. - this.getAll = function() { - var style = OT.$.clone(_style); - - for (var key in style) { - if(!style.hasOwnProperty(key)) { - continue; - } - if (OT.$.arrayIndexOf(_COMPONENT_STYLES, key) < 0) { - - // Strip unnecessary properties out, should this happen on Set? - delete style[key]; - } - } - - return style; - }; - - this.get = function(key) { - if (key) { - return _style[key]; - } - - // We haven't been asked for any specific key, just return the lot - return this.getAll(); - }; - - // *note:* this will not trigger onStyleChange if +silent+ is truthy - this.setAll = function(newStyles, silent) { - var oldValue, newValue; - - for (var key in newStyles) { - if(!newStyles.hasOwnProperty(key)) { - continue; - } - newValue = castValue(newStyles[key]); - - if (isValidStyle(key, newValue)) { - oldValue = _style[key]; - - if (newValue !== oldValue) { - _style[key] = newValue; - if (!silent) onStyleChange(key, newValue, oldValue); - } - - } else { - OT.warn('Style.setAll::Invalid style property passed ' + key + ' : ' + newValue); - } - } - - return this; - }; - - this.set = function(key, value) { - OT.debug('setStyle: ' + key.toString()); - - var newValue = castValue(value), - oldValue; - - if (!isValidStyle(key, newValue)) { - OT.warn('Style.set::Invalid style property passed ' + key + ' : ' + newValue); - return this; - } - - oldValue = _style[key]; - if (newValue !== oldValue) { - _style[key] = newValue; - - onStyleChange(key, value, oldValue); - } - - return this; - }; - - - if (initalStyles) this.setAll(initalStyles, true); - }; - -})(window); -!(function() { +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ /* - * A Publishers Microphone. + * A RTCPeerConnection.getStats based audio level sampler. * - * TODO - * * bind to changes in mute/unmute/volume/etc and respond to them + * It uses the the getStats method to get the audioOutputLevel. + * This implementation expects the single parameter version of the getStats method. + * + * Currently the audioOutputLevel stats is only supported in Chrome. + * + * @param {OT.SubscriberPeerConnection} peerConnection the peer connection to use to get the stats + * @constructor */ - OT.Microphone = function(webRTCStream, muted) { - var _muted; +OT.GetStatsAudioLevelSampler = function(peerConnection) { - OT.$.defineProperties(this, { - muted: { - get: function() { - return _muted; - }, - set: function(muted) { - if (_muted === muted) return; + if (!OT.$.hasCapabilities('audioOutputLevelStat')) { + throw new Error('The current platform does not provide the required capabilities'); + } - _muted = muted; + var _peerConnection = peerConnection, + _statsProperty = 'audioOutputLevel'; - var audioTracks = webRTCStream.getAudioTracks(); - - for (var i=0, num=audioTracks.length; inull if no value could be acquired + */ + this.sample = function(done) { + _peerConnection.getStats(function(error, stats) { + if (!error) { + for (var idx = 0; idx < stats.length; idx++) { + var stat = stats[idx]; + var audioOutputLevel = parseFloat(stat[_statsProperty]); + if (!isNaN(audioOutputLevel)) { + // the mex value delivered by getStats for audio levels is 2^15 + done(audioOutputLevel / 32768); + return; } } } + + done(null); }); + }; +}; - // Set the initial value - if (muted !== undefined) { - this.muted(muted === true); - } else if (webRTCStream.getAudioTracks().length) { - this.muted(!webRTCStream.getAudioTracks()[0].enabled); +/* + * An AudioContext based audio level sampler. It returns the maximum value in the + * last 1024 samples. + * + * It is worth noting that the remote MediaStream audio analysis is currently only + * available in FF. + * + * This implementation gracefully handles the case where the MediaStream has not + * been set yet by returning a null value until the stream is set. It is up to the + * call site to decide what to do with this value (most likely ignore it and retry later). + * + * @constructor + * @param {AudioContext} audioContext an audio context instance to get an analyser node + */ +OT.AnalyserAudioLevelSampler = function(audioContext) { - } else { - this.muted(false); + var _sampler = this, + _analyser = null, + _timeDomainData = null; + + var _getAnalyser = function(stream) { + var sourceNode = audioContext.createMediaStreamSource(stream); + var analyser = audioContext.createAnalyser(); + sourceNode.connect(analyser); + return analyser; + }; + + this.webRTCStream = null; + + this.sample = function(done) { + + if (!_analyser && _sampler.webRTCStream) { + _analyser = _getAnalyser(_sampler.webRTCStream); + _timeDomainData = new Uint8Array(_analyser.frequencyBinCount); } + if (_analyser) { + _analyser.getByteTimeDomainData(_timeDomainData); + + // varies from 0 to 255 + var max = 0; + for (var idx = 0; idx < _timeDomainData.length; idx++) { + max = Math.max(max, Math.abs(_timeDomainData[idx] - 128)); + } + + // normalize the collected level to match the range delivered by + // the getStats' audioOutputLevel + done(max / 128); + } else { + done(null); + } + }; +}; + +/* + * Transforms a raw audio level to produce a "smoother" animation when using displaying the + * audio level. This transformer is state-full because it needs to keep the previous average + * value of the signal for filtering. + * + * It applies a low pass filter to get rid of level jumps and apply a log scale. + * + * @constructor + */ +OT.AudioLevelTransformer = function() { + + var _averageAudioLevel = null; + + /* + * + * @param {number} audioLevel a level in the [0,1] range + * @returns {number} a level in the [0,1] range transformed + */ + this.transform = function(audioLevel) { + if (_averageAudioLevel === null || audioLevel >= _averageAudioLevel) { + _averageAudioLevel = audioLevel; + } else { + // a simple low pass filter with a smoothing of 70 + _averageAudioLevel = audioLevel * 0.3 + _averageAudioLevel * 0.7; + } + + // 1.5 scaling to map -30-0 dBm range to [0,1] + var logScaled = (Math.log(_averageAudioLevel) / Math.LN10) / 1.5 + 1; + + return Math.min(Math.max(logScaled, 0), 1); + }; +}; + +// tb_require('../helpers/helpers.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ + +/* + * Lazy instantiates an audio context and always return the same instance on following calls + * + * @returns {AudioContext} + */ +OT.audioContext = function() { + var context = new window.AudioContext(); + OT.audioContext = function() { + return context; + }; + return context; +}; + +// tb_require('../helpers/helpers.js') +// tb_require('./events.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ + +OT.Archive = function(id, name, status) { + this.id = id; + this.name = name; + this.status = status; + + this._ = {}; + + OT.$.eventing(this); + + // Mass update, called by Raptor.Dispatcher + this._.update = OT.$.bind(function (attributes) { + for (var key in attributes) { + if(!attributes.hasOwnProperty(key)) { + continue; + } + var oldValue = this[key]; + this[key] = attributes[key]; + + var event = new OT.ArchiveUpdatedEvent(this, key, oldValue, this[key]); + this.dispatchEvent(event); + } + }, this); + + this.destroy = function() {}; + +}; + +// tb_require('../helpers/helpers.js') +// tb_require('../helpers/lib/properties.js') +// tb_require('../helpers/lib/analytics.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ + +OT.analytics = new OT.Analytics(OT.properties.loggingURL); +// tb_require('../helpers/helpers.js') +// tb_require('../helpers/lib/widget_view.js') +// tb_require('./analytics.js') +// tb_require('./events.js') +// tb_require('./system_requirements.js') +// tb_require('./stylable_component.js') +// tb_require('./stream.js') +// tb_require('./connection.js') +// tb_require('./subscribing_state.js') +// tb_require('./environment_loader.js') +// tb_require('./audio_level_samplers.js') +// tb_require('./audio_context.js') +// tb_require('./chrome/chrome.js') +// tb_require('./chrome/backing_bar.js') +// tb_require('./chrome/name_panel.js') +// tb_require('./chrome/mute_button.js') +// tb_require('./chrome/archiving.js') +// tb_require('./chrome/audio_level_meter.js') +// tb_require('./peer_connection/subscriber_peer_connection.js') +// tb_require('./peer_connection/get_stats_adapter.js') +// tb_require('./peer_connection/get_stats_helpers.js') + + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ + +/** + * The Subscriber object is a representation of the local video element that is playing back + * a remote stream. The Subscriber object includes methods that let you disable and enable + * local audio playback for the subscribed stream. The subscribe() method of the + * {@link Session} object returns a Subscriber object. + * + * @property {Element} element The HTML DOM element containing the Subscriber. + * @property {String} id The DOM ID of the Subscriber. + * @property {Stream} stream The stream to which you are subscribing. + * + * @class Subscriber + * @augments EventDispatcher + */ +OT.Subscriber = function(targetElement, options, completionHandler) { + var _widgetId = OT.$.uuid(), + _domId = targetElement || _widgetId, + _container, + _streamContainer, + _chrome, + _audioLevelMeter, + _fromConnectionId, + _peerConnection, + _session = options.session, + _stream = options.stream, + _subscribeStartTime, + _startConnectingTime, + _properties, + _audioVolume = 100, + _state, + _prevStats, + _lastSubscribeToVideoReason, + _audioLevelCapable = OT.$.hasCapabilities('audioOutputLevelStat') || + OT.$.hasCapabilities('webAudioCapableRemoteStream'), + _audioLevelSampler, + _audioLevelRunner, + _frameRateRestricted = false, + _connectivityAttemptPinger, + _subscriber = this; + + _properties = OT.$.defaults({}, options, { + showControls: true, + fitMode: _stream.defaultFitMode || 'cover' + }); + + this.id = _domId; + this.widgetId = _widgetId; + this.session = _session; + this.stream = _stream = _properties.stream; + this.streamId = _stream.id; + + _prevStats = { + timeStamp: OT.$.now() }; -})(window); -!(function(window, OT) { + if (!_session) { + OT.handleJsException('Subscriber must be passed a session option', 2000, { + session: _session, + target: this + }); - // A Factory method for generating simple state machine classes. - // - // @usage - // var StateMachine = OT.generateSimpleStateMachine('start', ['start', 'middle', 'end', { - // start: ['middle'], - // middle: ['end'], - // end: ['start'] - // }]); - // - // var states = new StateMachine(); - // state.current; // <-- start - // state.set('middle'); - // - OT.generateSimpleStateMachine = function(initialState, states, transitions) { - var validStates = states.slice(), - validTransitions = OT.$.clone(transitions); + return; + } - var isValidState = function (state) { - return OT.$.arrayIndexOf(validStates, state) !== -1; - }; + OT.$.eventing(this, false); - var isValidTransition = function(fromState, toState) { - return validTransitions[fromState] && - OT.$.arrayIndexOf(validTransitions[fromState], toState) !== -1; - }; + if (typeof completionHandler === 'function') { + this.once('subscribeComplete', completionHandler); + } - return function(stateChangeFailed) { - var currentState = initialState, - previousState = null; + if(_audioLevelCapable) { + this.on({ + 'audioLevelUpdated:added': function(count) { + if (count === 1 && _audioLevelRunner) { + _audioLevelRunner.start(); + } + }, + 'audioLevelUpdated:removed': function(count) { + if (count === 0 && _audioLevelRunner) { + _audioLevelRunner.stop(); + } + } + }); + } - this.current = currentState; + var logAnalyticsEvent = function(action, variation, payload, throttle) { + var args = [{ + action: action, + variation: variation, + payload: payload, + streamId: _stream ? _stream.id : null, + sessionId: _session ? _session.sessionId : null, + connectionId: _session && _session.isConnected() ? + _session.connection.connectionId : null, + partnerId: _session && _session.isConnected() ? _session.sessionInfo.partnerId : null, + subscriberId: _widgetId, + }]; + if (throttle) args.push(throttle); + OT.analytics.logEvent.apply(OT.analytics, args); + }, - function signalChangeFailed(message, newState) { - stateChangeFailed({ - message: message, - newState: newState, - currentState: currentState, - previousState: previousState + logConnectivityEvent = function(variation, payload) { + if (variation === 'Attempt' || !_connectivityAttemptPinger) { + _connectivityAttemptPinger = new OT.ConnectivityAttemptPinger({ + action: 'Subscribe', + sessionId: _session ? _session.sessionId : null, + connectionId: _session && _session.isConnected() ? + _session.connection.connectionId : null, + partnerId: _session.isConnected() ? _session.sessionInfo.partnerId : null, + streamId: _stream ? _stream.id : null + }); + } + _connectivityAttemptPinger.setVariation(variation); + logAnalyticsEvent('Subscribe', variation, payload); + }, + + recordQOS = OT.$.bind(function(parsedStats) { + var QoSBlob = { + streamType : 'WebRTC', + width: _container ? Number(OT.$.width(_container.domElement).replace('px', '')) : null, + height: _container ? Number(OT.$.height(_container.domElement).replace('px', '')) : null, + sessionId: _session ? _session.sessionId : null, + connectionId: _session ? _session.connection.connectionId : null, + mediaServerName: _session ? _session.sessionInfo.messagingServer : null, + p2pFlag: _session ? _session.sessionInfo.p2pEnabled : false, + partnerId: _session ? _session.apiKey : null, + streamId: _stream.id, + subscriberId: _widgetId, + version: OT.properties.version, + duration: parseInt(OT.$.now() - _subscribeStartTime, 10), + remoteConnectionId: _stream.connection.connectionId + }; + + OT.analytics.logQOS( OT.$.extend(QoSBlob, parsedStats) ); + this.trigger('qos', parsedStats); + }, this), + + + stateChangeFailed = function(changeFailed) { + OT.error('Subscriber State Change Failed: ', changeFailed.message); + OT.debug(changeFailed); + }, + + onLoaded = function() { + if (_state.isSubscribing() || !_streamContainer) return; + + OT.debug('OT.Subscriber.onLoaded'); + + _state.set('Subscribing'); + _subscribeStartTime = OT.$.now(); + + var payload = { + pcc: parseInt(_subscribeStartTime - _startConnectingTime, 10), + hasRelayCandidates: _peerConnection && _peerConnection.hasRelayCandidates() + }; + logAnalyticsEvent('createPeerConnection', 'Success', payload); + + _container.loading(false); + _chrome.showAfterLoading(); + + if(_frameRateRestricted) { + _stream.setRestrictFrameRate(true); + } + + if(_audioLevelMeter && _subscriber.getStyle('audioLevelDisplayMode') === 'auto') { + _audioLevelMeter[_container.audioOnly() ? 'show' : 'hide'](); + } + + this.trigger('subscribeComplete', null, this); + this.trigger('loaded', this); + + logConnectivityEvent('Success', {streamId: _stream.id}); + }, + + onDisconnected = function() { + OT.debug('OT.Subscriber has been disconnected from the Publisher\'s PeerConnection'); + + if (_state.isAttemptingToSubscribe()) { + // subscribing error + _state.set('Failed'); + this.trigger('subscribeComplete', new OT.Error(null, 'ClientDisconnected')); + + } else if (_state.isSubscribing()) { + _state.set('Failed'); + + // we were disconnected after we were already subscribing + // probably do nothing? + } + + this.disconnect(); + }, + + onPeerConnectionFailure = OT.$.bind(function(code, reason, peerConnection, prefix) { + var payload; + if (_state.isAttemptingToSubscribe()) { + // We weren't subscribing yet so this was a failure in setting + // up the PeerConnection or receiving the initial stream. + payload = { + reason: prefix ? prefix : 'PeerConnectionError', + message: 'Subscriber PeerConnection Error: ' + reason, + hasRelayCandidates: _peerConnection && _peerConnection.hasRelayCandidates() + }; + logAnalyticsEvent('createPeerConnection', 'Failure', payload); + + _state.set('Failed'); + this.trigger('subscribeComplete', new OT.Error(null, reason)); + + } else if (_state.isSubscribing()) { + // we were disconnected after we were already subscribing + _state.set('Failed'); + this.trigger('error', reason); + } + + this.disconnect(); + + payload = { + reason: prefix ? prefix : 'PeerConnectionError', + message: 'Subscriber PeerConnection Error: ' + reason, + code: OT.ExceptionCodes.P2P_CONNECTION_FAILED + }; + logConnectivityEvent('Failure', payload); + + OT.handleJsException('Subscriber PeerConnection Error: ' + reason, + OT.ExceptionCodes.P2P_CONNECTION_FAILED, { + session: _session, + target: this + } + ); + _showError.call(this, reason); + }, this), + + onRemoteStreamAdded = function(webRTCStream) { + OT.debug('OT.Subscriber.onRemoteStreamAdded'); + + _state.set('BindingRemoteStream'); + + // Disable the audio/video, if needed + this.subscribeToAudio(_properties.subscribeToAudio); + + _lastSubscribeToVideoReason = 'loading'; + this.subscribeToVideo(_properties.subscribeToVideo, 'loading'); + + var videoContainerOptions = { + error: onPeerConnectionFailure, + audioVolume: _audioVolume + }; + + // This is a workaround for a bug in Chrome where a track disabled on + // the remote end doesn't fire loadedmetadata causing the subscriber to timeout + // https://jira.tokbox.com/browse/OPENTOK-15605 + // Also https://jira.tokbox.com/browse/OPENTOK-16425 + var tracks, + reenableVideoTrack = false; + if (!_stream.hasVideo && OT.$.env.name === 'Chrome' && OT.$.env.version >= 35) { + tracks = webRTCStream.getVideoTracks(); + if(tracks.length > 0) { + tracks[0].enabled = false; + reenableVideoTrack = tracks[0]; + } + } + + _streamContainer = _container.bindVideo(webRTCStream, + videoContainerOptions, + OT.$.bind(function(err) { + if (err) { + onPeerConnectionFailure(null, err.message || err, _peerConnection, 'VideoElement'); + return; + } + + // Continues workaround for https://jira.tokbox.com/browse/OPENTOK-15605 + // Also https://jira.tokbox.com/browse/OPENTOK-16425] + if (reenableVideoTrack != null && _properties.subscribeToVideo) { + reenableVideoTrack.enabled = true; + } + + _streamContainer.orientation({ + width: _stream.videoDimensions.width, + height: _stream.videoDimensions.height, + videoOrientation: _stream.videoDimensions.orientation + }); + + onLoaded.call(this, null); + }, this)); + + if (OT.$.hasCapabilities('webAudioCapableRemoteStream') && _audioLevelSampler && + webRTCStream.getAudioTracks().length > 0) { + _audioLevelSampler.webRTCStream = webRTCStream; + } + + logAnalyticsEvent('createPeerConnection', 'StreamAdded'); + this.trigger('streamAdded', this); + }, + + onRemoteStreamRemoved = function(webRTCStream) { + OT.debug('OT.Subscriber.onStreamRemoved'); + + if (_streamContainer.stream === webRTCStream) { + _streamContainer.destroy(); + _streamContainer = null; + } + + + this.trigger('streamRemoved', this); + }, + + streamDestroyed = function () { + this.disconnect(); + }, + + streamUpdated = function(event) { + + switch(event.changedProperty) { + case 'videoDimensions': + if (!_streamContainer) { + // Ignore videoEmension updates before streamContainer is created OPENTOK-17253 + break; + } + _streamContainer.orientation({ + width: event.newValue.width, + height: event.newValue.height, + videoOrientation: event.newValue.orientation + }); + + this.dispatchEvent(new OT.VideoDimensionsChangedEvent( + this, event.oldValue, event.newValue + )); + + break; + + case 'videoDisableWarning': + _chrome.videoDisabledIndicator.setWarning(event.newValue); + this.dispatchEvent(new OT.VideoDisableWarningEvent( + event.newValue ? 'videoDisableWarning' : 'videoDisableWarningLifted' + )); + break; + + case 'hasVideo': + + setAudioOnly(!(_stream.hasVideo && _properties.subscribeToVideo)); + + this.dispatchEvent(new OT.VideoEnabledChangedEvent( + _stream.hasVideo ? 'videoEnabled' : 'videoDisabled', { + reason: 'publishVideo' + })); + break; + + case 'hasAudio': + // noop + } + }, + + /// Chrome + + // If mode is false, then that is the mode. If mode is true then we'll + // definitely display the button, but we'll defer the model to the + // Publishers buttonDisplayMode style property. + chromeButtonMode = function(mode) { + if (mode === false) return 'off'; + + var defaultMode = this.getStyle('buttonDisplayMode'); + + // The default model is false, but it's overridden by +mode+ being true + if (defaultMode === false) return 'on'; + + // defaultMode is either true or auto. + return defaultMode; + }, + + updateChromeForStyleChange = function(key, value/*, oldValue*/) { + if (!_chrome) return; + + switch(key) { + case 'nameDisplayMode': + _chrome.name.setDisplayMode(value); + _chrome.backingBar.setNameMode(value); + break; + + case 'videoDisabledDisplayMode': + _chrome.videoDisabledIndicator.setDisplayMode(value); + break; + + case 'showArchiveStatus': + _chrome.archive.setShowArchiveStatus(value); + break; + + case 'buttonDisplayMode': + _chrome.muteButton.setDisplayMode(value); + _chrome.backingBar.setMuteMode(value); + break; + + case 'audioLevelDisplayMode': + _chrome.audioLevel.setDisplayMode(value); + break; + + case 'bugDisplayMode': + // bugDisplayMode can't be updated but is used by some partners + + case 'backgroundImageURI': + _container.setBackgroundImageURI(value); + } + }, + + _createChrome = function() { + + var widgets = { + backingBar: new OT.Chrome.BackingBar({ + nameMode: !_properties.name ? 'off' : this.getStyle('nameDisplayMode'), + muteMode: chromeButtonMode.call(this, this.getStyle('showMuteButton')) + }), + + name: new OT.Chrome.NamePanel({ + name: _properties.name, + mode: this.getStyle('nameDisplayMode') + }), + + muteButton: new OT.Chrome.MuteButton({ + muted: _properties.muted, + mode: chromeButtonMode.call(this, this.getStyle('showMuteButton')) + }), + + archive: new OT.Chrome.Archiving({ + show: this.getStyle('showArchiveStatus'), + archiving: false + }) + }; + + if (_audioLevelCapable) { + var audioLevelTransformer = new OT.AudioLevelTransformer(); + + var audioLevelUpdatedHandler = function(evt) { + _audioLevelMeter.setValue(audioLevelTransformer.transform(evt.audioLevel)); + }; + + _audioLevelMeter = new OT.Chrome.AudioLevelMeter({ + mode: this.getStyle('audioLevelDisplayMode'), + onActivate: function() { + _subscriber.on('audioLevelUpdated', audioLevelUpdatedHandler); + }, + onPassivate: function() { + _subscriber.off('audioLevelUpdated', audioLevelUpdatedHandler); + } + }); + + widgets.audioLevel = _audioLevelMeter; + } + + widgets.videoDisabledIndicator = new OT.Chrome.VideoDisabledIndicator({ + mode: this.getStyle('videoDisabledDisplayMode') }); - } - // Validates +newState+. If it's invalid it triggers stateChangeFailed and returns false. - function handleInvalidStateChanges(newState) { - if (!isValidState(newState)) { - signalChangeFailed('\'' + newState + '\' is not a valid state', newState); + _chrome = new OT.Chrome({ + parent: _container.domElement + }).set(widgets).on({ + muted: function() { + muteAudio.call(this, true); + }, - return false; + unmuted: function() { + muteAudio.call(this, false); + } + }, this); + + // Hide the chrome until we explicitly show it + _chrome.hideWhileLoading(); + }, + + _showError = function() { + // Display the error message inside the container, assuming it's + // been created by now. + if (_container) { + _container.addError( + 'The stream was unable to connect due to a network error.', + 'Make sure your connection isn\'t blocked by a firewall.' + ); } - - if (!isValidTransition(currentState, newState)) { - signalChangeFailed('\'' + currentState + '\' cannot transition to \'' + - newState + '\'', newState); - - return false; - } - - return true; - } - - - this.set = function(newState) { - if (!handleInvalidStateChanges(newState)) return; - previousState = currentState; - this.current = currentState = newState; }; + OT.StylableComponent(this, { + nameDisplayMode: 'auto', + buttonDisplayMode: 'auto', + audioLevelDisplayMode: 'auto', + videoDisabledDisplayMode: 'auto', + backgroundImageURI: null, + showArchiveStatus: true, + showMicButton: true + }, _properties.showControls, function (payload) { + logAnalyticsEvent('SetStyle', 'Subscriber', payload, 0.1); + }); + + var setAudioOnly = function(audioOnly) { + if (_container) { + _container.audioOnly(audioOnly); + _container.showPoster(audioOnly); + } + + if (_audioLevelMeter && _subscriber.getStyle('audioLevelDisplayMode') === 'auto') { + _audioLevelMeter[audioOnly ? 'show' : 'hide'](); + } + }; + + // logs an analytics event for getStats every 100 calls + var notifyGetStatsCalled = (function() { + var callCount = 0; + return function throttlingNotifyGetStatsCalled() { + if (callCount % 100 === 0) { + logAnalyticsEvent('getStats', 'Called'); + } + callCount++; }; + })(); + + this.destroy = function(reason, quiet) { + if (_state.isDestroyed()) return; + + if(reason === 'streamDestroyed') { + if (_state.isAttemptingToSubscribe()) { + // We weren't subscribing yet so the stream was destroyed before we setup + // the PeerConnection or receiving the initial stream. + this.trigger('subscribeComplete', new OT.Error(null, 'InvalidStreamID')); + } + } + + _state.set('Destroyed'); + + if(_audioLevelRunner) { + _audioLevelRunner.stop(); + } + + this.disconnect(); + + if (_chrome) { + _chrome.destroy(); + _chrome = null; + } + + if (_container) { + _container.destroy(); + _container = null; + this.element = null; + } + + if (_stream && !_stream.destroyed) { + logAnalyticsEvent('unsubscribe', null, {streamId: _stream.id}); + } + + this.id = _domId = null; + this.stream = _stream = null; + this.streamId = null; + + this.session =_session = null; + _properties = null; + + if (quiet !== true) { + this.dispatchEvent( + new OT.DestroyedEvent( + OT.Event.names.SUBSCRIBER_DESTROYED, + this, + reason + ), + OT.$.bind(this.off, this) + ); + } + + return this; }; -})(window, window.OT); -!(function() { + this.disconnect = function() { + if (!_state.isDestroyed() && !_state.isFailed()) { + // If we are already in the destroyed state then disconnect + // has been called after (or from within) destroy. + _state.set('NotSubscribing'); + } -// Models a Subscriber's subscribing State -// -// Valid States: -// NotSubscribing (the initial state -// Init (basic setup of DOM -// ConnectingToPeer (Failure Cases -> No Route, Bad Offer, Bad Answer -// BindingRemoteStream (Failure Cases -> Anything to do with the media being -// (invalid, the media never plays -// Subscribing (this is 'onLoad' -// Failed (terminal state, with a reason that maps to one of the -// (failure cases above -// Destroyed (The subscriber has been cleaned up, terminal state -// -// -// Valid Transitions: -// NotSubscribing -> -// Init -// -// Init -> -// ConnectingToPeer -// | BindingRemoteStream (if we are subscribing to ourselves and we alreay -// (have a stream -// | NotSubscribing (destroy() -// -// ConnectingToPeer -> -// BindingRemoteStream -// | NotSubscribing -// | Failed -// | NotSubscribing (destroy() -// -// BindingRemoteStream -> -// Subscribing -// | Failed -// | NotSubscribing (destroy() -// -// Subscribing -> -// NotSubscribing (unsubscribe -// | Failed (probably a peer connection failure after we began -// (subscribing -// -// Failed -> -// Destroyed -// -// Destroyed -> (terminal state) -// -// -// @example -// var state = new SubscribingState(function(change) { -// console.log(change.message); -// }); -// -// state.set('Init'); -// state.current; -> 'Init' -// -// state.set('Subscribing'); -> triggers stateChangeFailed and logs out the error message -// -// - var validStates, - validTransitions, - initialState = 'NotSubscribing'; + if (_streamContainer) { + _streamContainer.destroy(); + _streamContainer = null; + } - validStates = [ - 'NotSubscribing', 'Init', 'ConnectingToPeer', - 'BindingRemoteStream', 'Subscribing', 'Failed', - 'Destroyed' - ]; + if (_peerConnection) { + _peerConnection.destroy(); + _peerConnection = null; - validTransitions = { - NotSubscribing: ['NotSubscribing', 'Init', 'Destroyed'], - Init: ['NotSubscribing', 'ConnectingToPeer', 'BindingRemoteStream', 'Destroyed'], - ConnectingToPeer: ['NotSubscribing', 'BindingRemoteStream', 'Failed', 'Destroyed'], - BindingRemoteStream: ['NotSubscribing', 'Subscribing', 'Failed', 'Destroyed'], - Subscribing: ['NotSubscribing', 'Failed', 'Destroyed'], - Failed: ['Destroyed'], - Destroyed: [] + logAnalyticsEvent('disconnect', 'PeerConnection', {streamId: _stream.id}); + } }; - OT.SubscribingState = OT.generateSimpleStateMachine(initialState, validStates, validTransitions); + this.processMessage = function(type, fromConnection, message) { + OT.debug('OT.Subscriber.processMessage: Received ' + type + ' message from ' + + fromConnection.id); + OT.debug(message); - OT.SubscribingState.prototype.isDestroyed = function() { - return this.current === 'Destroyed'; + if (_fromConnectionId !== fromConnection.id) { + _fromConnectionId = fromConnection.id; + } + + if (_peerConnection) { + _peerConnection.processMessage(type, message); + } }; - OT.SubscribingState.prototype.isFailed = function() { - return this.current === 'Failed'; + this.disableVideo = function(active) { + if (!active) { + OT.warn('Due to high packet loss and low bandwidth, video has been disabled'); + } else { + if (_lastSubscribeToVideoReason === 'auto') { + OT.info('Video has been re-enabled'); + _chrome.videoDisabledIndicator.disableVideo(false); + } else { + OT.info('Video was not re-enabled because it was manually disabled'); + return; + } + } + this.subscribeToVideo(active, 'auto'); + if(!active) { + _chrome.videoDisabledIndicator.disableVideo(true); + } + var payload = active ? {videoEnabled: true} : {videoDisabled: true}; + logAnalyticsEvent('updateQuality', 'video', payload); }; - OT.SubscribingState.prototype.isSubscribing = function() { - return this.current === 'Subscribing'; + /** + * Return the base-64-encoded string of PNG data representing the Subscriber video. + * + *

You can use the string as the value for a data URL scheme passed to the src parameter of + * an image file, as in the following:

+ * + *
+   *  var imgData = subscriber.getImgData();
+   *
+   *  var img = document.createElement("img");
+   *  img.setAttribute("src", "data:image/png;base64," + imgData);
+   *  var imgWin = window.open("about:blank", "Screenshot");
+   *  imgWin.document.write("<body></body>");
+   *  imgWin.document.body.appendChild(img);
+   *  
+ * @method #getImgData + * @memberOf Subscriber + * @return {String} The base-64 encoded string. Returns an empty string if there is no video. + */ + this.getImgData = function() { + if (!this.isSubscribing()) { + OT.error('OT.Subscriber.getImgData: Cannot getImgData before the Subscriber ' + + 'is subscribing.'); + return null; + } + + return _streamContainer.imgData(); }; - OT.SubscribingState.prototype.isAttemptingToSubscribe = function() { - return OT.$.arrayIndexOf( - [ 'Init', 'ConnectingToPeer', 'BindingRemoteStream' ], - this.current - ) !== -1; + this.getStats = function(callback) { + if (!_peerConnection) { + callback(new OT.$.Error('Subscriber is not connected cannot getStats', 1015)); + return; + } + + notifyGetStatsCalled(); + + _peerConnection.getStats(function(error, stats) { + if (error) { + callback(error); + return; + } + + var otStats = { + timestamp: 0 + }; + + OT.$.forEach(stats, function(stat) { + if (OT.getStatsHelpers.isInboundStat(stat)) { + var video = OT.getStatsHelpers.isVideoStat(stat); + var audio = OT.getStatsHelpers.isAudioStat(stat); + + // it is safe to override the timestamp of one by another + // if they are from the same getStats "batch" video and audio ts have the same value + if (audio || video) { + otStats.timestamp = OT.getStatsHelpers.normalizeTimestamp(stat.timestamp); + } + if (video) { + otStats.video = OT.getStatsHelpers.parseStatCategory(stat); + } else if (audio) { + otStats.audio = OT.getStatsHelpers.parseStatCategory(stat); + } + } + }); + + callback(null, otStats); + }); }; -})(window); -!(function() { + /** + * Sets the audio volume, between 0 and 100, of the Subscriber. + * + *

You can set the initial volume when you call the Session.subscribe() + * method. Pass a audioVolume property of the properties parameter + * of the method.

+ * + * @param {Number} value The audio volume, between 0 and 100. + * + * @return {Subscriber} The Subscriber object. This lets you chain method calls, as in the + * following: + * + *
mySubscriber.setAudioVolume(50).setStyle(newStyle);
+ * + * @see getAudioVolume() + * @see Session.subscribe() + * @method #setAudioVolume + * @memberOf Subscriber + */ + this.setAudioVolume = function(value) { + value = parseInt(value, 10); + if (isNaN(value)) { + OT.error('OT.Subscriber.setAudioVolume: value should be an integer between 0 and 100'); + return this; + } + _audioVolume = Math.max(0, Math.min(100, value)); + if (_audioVolume !== value) { + OT.warn('OT.Subscriber.setAudioVolume: value should be an integer between 0 and 100'); + } + if(_properties.muted && _audioVolume > 0) { + _properties.premuteVolume = value; + muteAudio.call(this, false); + } + if (_streamContainer) { + _streamContainer.setAudioVolume(_audioVolume); + } + return this; + }; -// Models a Publisher's publishing State -// -// Valid States: -// NotPublishing -// GetUserMedia -// BindingMedia -// MediaBound -// PublishingToSession -// Publishing -// Failed -// Destroyed -// -// -// Valid Transitions: -// NotPublishing -> -// GetUserMedia -// -// GetUserMedia -> -// BindingMedia -// | Failed (Failure Reasons -> stream error, constraints, -// (permission denied -// | NotPublishing (destroy() -// -// -// BindingMedia -> -// MediaBound -// | Failed (Failure Reasons -> Anything to do with the media -// (being invalid, the media never plays -// | NotPublishing (destroy() -// -// MediaBound -> -// PublishingToSession (MediaBound could transition to PublishingToSession -// (if a stand-alone publish is bound to a session -// | Failed (Failure Reasons -> media issues with a stand-alone publisher -// | NotPublishing (destroy() -// -// PublishingToSession -// Publishing -// | Failed (Failure Reasons -> timeout while waiting for ack of -// (stream registered. We do not do this right now -// | NotPublishing (destroy() -// -// -// Publishing -> -// NotPublishing (Unpublish -// | Failed (Failure Reasons -> loss of network, media error, anything -// (that causes *all* Peer Connections to fail (less than all -// (failing is just an error, all is failure) -// | NotPublishing (destroy() -// -// Failed -> -// Destroyed -// -// Destroyed -> (Terminal state -// -// + /** + * Returns the audio volume, between 0 and 100, of the Subscriber. + * + *

Generally you use this method in conjunction with the setAudioVolume() + * method.

+ * + * @return {Number} The audio volume, between 0 and 100, of the Subscriber. + * @see setAudioVolume() + * @method #getAudioVolume + * @memberOf Subscriber + */ + this.getAudioVolume = function() { + if(_properties.muted) { + return 0; + } + if (_streamContainer) return _streamContainer.getAudioVolume(); + else return _audioVolume; + }; - var validStates = [ - 'NotPublishing', 'GetUserMedia', 'BindingMedia', 'MediaBound', - 'PublishingToSession', 'Publishing', 'Failed', - 'Destroyed' - ], + /** + * Toggles audio on and off. Starts subscribing to audio (if it is available and currently + * not being subscribed to) when the value is true; stops + * subscribing to audio (if it is currently being subscribed to) when the value + * is false. + *

+ * Note: This method only affects the local playback of audio. It has no impact on the + * audio for other connections subscribing to the same stream. If the Publsher is not + * publishing audio, enabling the Subscriber audio will have no practical effect. + *

+ * + * @param {Boolean} value Whether to start subscribing to audio (true) or not + * (false). + * + * @return {Subscriber} The Subscriber object. This lets you chain method calls, as in the + * following: + * + *
mySubscriber.subscribeToAudio(true).subscribeToVideo(false);
+ * + * @see subscribeToVideo() + * @see Session.subscribe() + * @see StreamPropertyChangedEvent + * + * @method #subscribeToAudio + * @memberOf Subscriber + */ + this.subscribeToAudio = function(pValue) { + var value = OT.$.castToBoolean(pValue, true); - validTransitions = { - NotPublishing: ['NotPublishing', 'GetUserMedia', 'Destroyed'], - GetUserMedia: ['BindingMedia', 'Failed', 'NotPublishing', 'Destroyed'], - BindingMedia: ['MediaBound', 'Failed', 'NotPublishing', 'Destroyed'], - MediaBound: ['NotPublishing', 'PublishingToSession', 'Failed', 'Destroyed'], - PublishingToSession: ['NotPublishing', 'Publishing', 'Failed', 'Destroyed'], - Publishing: ['NotPublishing', 'MediaBound', 'Failed', 'Destroyed'], - Failed: ['Destroyed'], - Destroyed: [] + if (_peerConnection) { + _peerConnection.subscribeToAudio(value && !_properties.subscribeMute); + + if (_session && _stream && value !== _properties.subscribeToAudio) { + _stream.setChannelActiveState('audio', value && !_properties.subscribeMute); + } + } + + _properties.subscribeToAudio = value; + + return this; + }; + + var muteAudio = function(_mute) { + _chrome.muteButton.muted(_mute); + + if(_mute === _properties.mute) { + return; + } + if(OT.$.env.name === 'Chrome' || OTPlugin.isInstalled()) { + _properties.subscribeMute = _properties.muted = _mute; + this.subscribeToAudio(_properties.subscribeToAudio); + } else { + if(_mute) { + _properties.premuteVolume = this.getAudioVolume(); + _properties.muted = true; + this.setAudioVolume(0); + } else if(_properties.premuteVolume || _properties.audioVolume) { + _properties.muted = false; + this.setAudioVolume(_properties.premuteVolume || _properties.audioVolume); + } + } + _properties.mute = _properties.mute; + }; + + var reasonMap = { + auto: 'quality', + publishVideo: 'publishVideo', + subscribeToVideo: 'subscribeToVideo' + }; + + + /** + * Toggles video on and off. Starts subscribing to video (if it is available and + * currently not being subscribed to) when the value is true; + * stops subscribing to video (if it is currently being subscribed to) when the + * value is false. + *

+ * Note: This method only affects the local playback of video. It has no impact on + * the video for other connections subscribing to the same stream. If the Publsher is not + * publishing video, enabling the Subscriber video will have no practical video. + *

+ * + * @param {Boolean} value Whether to start subscribing to video (true) or not + * (false). + * + * @return {Subscriber} The Subscriber object. This lets you chain method calls, as in the + * following: + * + *
mySubscriber.subscribeToVideo(true).subscribeToAudio(false);
+ * + * @see subscribeToAudio() + * @see Session.subscribe() + * @see StreamPropertyChangedEvent + * + * @method #subscribeToVideo + * @memberOf Subscriber + */ + this.subscribeToVideo = function(pValue, reason) { + var value = OT.$.castToBoolean(pValue, true); + + setAudioOnly(!(value && _stream.hasVideo)); + + if ( value && _container && _container.video()) { + _container.loading(value); + _container.video().whenTimeIncrements(function() { + _container.loading(false); + }, this); + } + + if (_chrome && _chrome.videoDisabledIndicator) { + _chrome.videoDisabledIndicator.disableVideo(false); + } + + if (_peerConnection) { + _peerConnection.subscribeToVideo(value); + + if (_session && _stream && (value !== _properties.subscribeToVideo || + reason !== _lastSubscribeToVideoReason)) { + _stream.setChannelActiveState('video', value, reason); + } + } + + _properties.subscribeToVideo = value; + _lastSubscribeToVideoReason = reason; + + if (reason !== 'loading') { + this.dispatchEvent(new OT.VideoEnabledChangedEvent( + value ? 'videoEnabled' : 'videoDisabled', + { + reason: reasonMap[reason] || 'subscribeToVideo' + } + )); + } + + return this; + }; + + this.isSubscribing = function() { + return _state.isSubscribing(); + }; + + this.isWebRTC = true; + + this.isLoading = function() { + return _container && _container.loading(); + }; + + this.videoElement = function() { + return _streamContainer.domElement(); + }; + + this.videoWidth = function() { + return _streamContainer.videoWidth(); + }; + + this.videoHeight = function() { + return _streamContainer.videoHeight(); + }; + + /** + * Restricts the frame rate of the Subscriber's video stream, when you pass in + * true. When you pass in false, the frame rate of the video stream + * is not restricted. + *

+ * When the frame rate is restricted, the Subscriber video frame will update once or less per + * second. + *

+ * This feature is only available in sessions that use the OpenTok Media Router (sessions with + * the media mode + * set to routed), not in sessions with the media mode set to relayed. In relayed sessions, + * calling this method has no effect. + *

+ * Restricting the subscriber frame rate has the following benefits: + *

    + *
  • It reduces CPU usage.
  • + *
  • It reduces the network bandwidth consumed.
  • + *
  • It lets you subscribe to more streams simultaneously.
  • + *
+ *

+ * Reducing a subscriber's frame rate has no effect on the frame rate of the video in + * other clients. + * + * @param {Boolean} value Whether to restrict the Subscriber's video frame rate + * (true) or not (false). + * + * @return {Subscriber} The Subscriber object. This lets you chain method calls, as in the + * following: + * + *

mySubscriber.restrictFrameRate(false).subscribeToAudio(true);
+ * + * @method #restrictFrameRate + * @memberOf Subscriber + */ + this.restrictFrameRate = function(val) { + OT.debug('OT.Subscriber.restrictFrameRate(' + val + ')'); + + logAnalyticsEvent('restrictFrameRate', val.toString(), {streamId: _stream.id}); + + if (_session.sessionInfo.p2pEnabled) { + OT.warn('OT.Subscriber.restrictFrameRate: Cannot restrictFrameRate on a P2P session'); + } + + if (typeof val !== 'boolean') { + OT.error('OT.Subscriber.restrictFrameRate: expected a boolean value got a ' + typeof val); + } else { + _frameRateRestricted = val; + _stream.setRestrictFrameRate(val); + } + return this; + }; + + this.on('styleValueChanged', updateChromeForStyleChange, this); + + this._ = { + archivingStatus: function(status) { + if(_chrome) { + _chrome.archive.setArchiving(status); + } + } + }; + + _state = new OT.SubscribingState(stateChangeFailed); + + OT.debug('OT.Subscriber: subscribe to ' + _stream.id); + + _state.set('Init'); + + if (!_stream) { + // @todo error + OT.error('OT.Subscriber: No stream parameter.'); + return false; + } + + _stream.on({ + updated: streamUpdated, + destroyed: streamDestroyed + }, this); + + _fromConnectionId = _stream.connection.id; + _properties.name = _properties.name || _stream.name; + _properties.classNames = 'OT_root OT_subscriber'; + + if (_properties.style) { + this.setStyle(_properties.style, null, true); + } + if (_properties.audioVolume) { + this.setAudioVolume(_properties.audioVolume); + } + + _properties.subscribeToAudio = OT.$.castToBoolean(_properties.subscribeToAudio, true); + _properties.subscribeToVideo = OT.$.castToBoolean(_properties.subscribeToVideo, true); + + _container = new OT.WidgetView(targetElement, _properties); + this.id = _domId = _container.domId(); + this.element = _container.domElement; + + _createChrome.call(this); + + _startConnectingTime = OT.$.now(); + + if (_stream.connection.id !== _session.connection.id) { + logAnalyticsEvent('createPeerConnection', 'Attempt', ''); + + _state.set('ConnectingToPeer'); + + _peerConnection = new OT.SubscriberPeerConnection(_stream.connection, _session, + _stream, this, _properties); + + _peerConnection.on({ + disconnected: onDisconnected, + error: onPeerConnectionFailure, + remoteStreamAdded: onRemoteStreamAdded, + remoteStreamRemoved: onRemoteStreamRemoved, + qos: recordQOS + }, this); + + // initialize the peer connection AFTER we've added the event listeners + _peerConnection.init(); + + if (OT.$.hasCapabilities('audioOutputLevelStat')) { + _audioLevelSampler = new OT.GetStatsAudioLevelSampler(_peerConnection, 'out'); + } else if (OT.$.hasCapabilities('webAudioCapableRemoteStream')) { + _audioLevelSampler = new OT.AnalyserAudioLevelSampler(OT.audioContext()); + } + + if(_audioLevelSampler) { + var subscriber = this; + // sample with interval to minimise disturbance on animation loop but dispatch the + // event with RAF since the main purpose is animation of a meter + _audioLevelRunner = new OT.IntervalRunner(function() { + _audioLevelSampler.sample(function(audioOutputLevel) { + if (audioOutputLevel !== null) { + OT.$.requestAnimationFrame(function() { + subscriber.dispatchEvent( + new OT.AudioLevelUpdatedEvent(audioOutputLevel)); + }); + } + }); + }, 60); + } + } else { + logAnalyticsEvent('createPeerConnection', 'Attempt', ''); + + var publisher = _session.getPublisherForStream(_stream); + if(!(publisher && publisher._.webRtcStream())) { + this.trigger('subscribeComplete', new OT.Error(null, 'InvalidStreamID')); + return this; + } + + // Subscribe to yourself edge-case + onRemoteStreamAdded.call(this, publisher._.webRtcStream()); + } + + logConnectivityEvent('Attempt', {streamId: _stream.id}); + + + /** + * Dispatched periodically to indicate the subscriber's audio level. The event is dispatched + * up to 60 times per second, depending on the browser. The audioLevel property + * of the event is audio level, from 0 to 1.0. See {@link AudioLevelUpdatedEvent} for more + * information. + *

+ * The following example adjusts the value of a meter element that shows volume of the + * subscriber. Note that the audio level is adjusted logarithmically and a moving average + * is applied: + *

+ * var movingAvg = null;
+ * subscriber.on('audioLevelUpdated', function(event) {
+ *   if (movingAvg === null || movingAvg <= event.audioLevel) {
+ *     movingAvg = event.audioLevel;
+ *   } else {
+ *     movingAvg = 0.7 * movingAvg + 0.3 * event.audioLevel;
+ *   }
+ *
+ *   // 1.5 scaling to map the -30 - 0 dBm range to [0,1]
+ *   var logLevel = (Math.log(movingAvg) / Math.LN10) / 1.5 + 1;
+ *   logLevel = Math.min(Math.max(logLevel, 0), 1);
+ *   document.getElementById('subscriberMeter').value = logLevel;
+ * });
+ * 
+ *

This example shows the algorithm used by the default audio level indicator displayed + * in an audio-only Subscriber. + * + * @name audioLevelUpdated + * @event + * @memberof Subscriber + * @see AudioLevelUpdatedEvent + */ + +/** +* Dispatched when the video for the subscriber is disabled. +*

+* The reason property defines the reason the video was disabled. This can be set to +* one of the following values: +*

+* +*

    +* +*
  • "publishVideo" — The publisher stopped publishing video by calling +* publishVideo(false).
  • +* +*
  • "quality" — The OpenTok Media Router stopped sending video +* to the subscriber based on stream quality changes. This feature of the OpenTok Media +* Router has a subscriber drop the video stream when connectivity degrades. (The subscriber +* continues to receive the audio stream, if there is one.) +*

    +* Before sending this event, when the Subscriber's stream quality deteriorates to a level +* that is low enough that the video stream is at risk of being disabled, the Subscriber +* dispatches a videoDisableWarning event. +*

    +* If connectivity improves to support video again, the Subscriber object dispatches +* a videoEnabled event, and the Subscriber resumes receiving video. +*

    +* By default, the Subscriber displays a video disabled indicator when a +* videoDisabled event with this reason is dispatched and removes the indicator +* when the videoDisabled event with this reason is dispatched. You can control +* the display of this icon by calling the setStyle() method of the Subscriber, +* setting the videoDisabledDisplayMode property(or you can set the style when +* calling the Session.subscribe() method, setting the style property +* of the properties parameter). +*

    +* This feature is only available in sessions that use the OpenTok Media Router (sessions with +* the media mode +* set to routed), not in sessions with the media mode set to relayed. +*

    +* You can disable this audio-only fallback feature, by setting the +* audioFallbackEnabled property to false in the options you pass +* into the OT.initPublisher() method on the publishing client. (See +* OT.initPublisher().) +*

  • +* +*
  • "subscribeToVideo" — The subscriber started or stopped subscribing to +* video, by calling subscribeToVideo(false). +*
  • +* +*
+* +* @see VideoEnabledChangedEvent +* @see event:videoDisableWarning +* @see event:videoEnabled +* @name videoDisabled +* @event +* @memberof Subscriber +*/ + +/** +* Dispatched when the OpenTok Media Router determines that the stream quality has degraded +* and the video will be disabled if the quality degrades more. If the quality degrades further, +* the Subscriber disables the video and dispatches a videoDisabled event. +*

+* By default, the Subscriber displays a video disabled warning indicator when this event +* is dispatched (and the video is disabled). You can control the display of this icon by +* calling the setStyle() method and setting the +* videoDisabledDisplayMode property (or you can set the style when calling +* the Session.subscribe() method and setting the style property +* of the properties parameter). +*

+* This feature is only available in sessions that use the OpenTok Media Router (sessions with +* the media mode +* set to routed), not in sessions with the media mode set to relayed. +* +* @see Event +* @see event:videoDisabled +* @see event:videoDisableWarningLifted +* @name videoDisableWarning +* @event +* @memberof Subscriber +*/ + +/** +* Dispatched when the OpenTok Media Router determines that the stream quality has improved +* to the point at which the video being disabled is not an immediate risk. This event is +* dispatched after the Subscriber object dispatches a videoDisableWarning event. +*

+* This feature is only available in sessions that use the OpenTok Media Router (sessions with +* the media mode +* set to routed), not in sessions with the media mode set to relayed. +* +* @see Event +* @see event:videoDisabled +* @see event:videoDisableWarning +* @name videoDisableWarningLifted +* @event +* @memberof Subscriber +*/ + +/** +* Dispatched when the OpenTok Media Router resumes sending video to the subscriber +* after video was previously disabled. +*

+* The reason property defines the reason the video was enabled. This can be set to +* one of the following values: +*

+* +*

    +* +*
  • "publishVideo" — The publisher started publishing video by calling +* publishVideo(true).
  • +* +*
  • "quality" — The OpenTok Media Router resumed sending video +* to the subscriber based on stream quality changes. This feature of the OpenTok Media +* Router has a subscriber drop the video stream when connectivity degrades and then resume +* the video stream if the stream quality improves. +*

    +* This feature is only available in sessions that use the OpenTok Media Router (sessions with +* the media mode +* set to routed), not in sessions with the media mode set to relayed. +*

  • +* +*
  • "subscribeToVideo" — The subscriber started or stopped subscribing to +* video, by calling subscribeToVideo(false). +*
  • +* +*
+* +*

+* To prevent video from resuming, in the videoEnabled event listener, +* call subscribeToVideo(false) on the Subscriber object. +* +* @see VideoEnabledChangedEvent +* @see event:videoDisabled +* @name videoEnabled +* @event +* @memberof Subscriber +*/ + +/** +* Dispatched when the Subscriber element is removed from the HTML DOM. When this event is +* dispatched, you may choose to adjust or remove HTML DOM elements related to the subscriber. +* @see Event +* @name destroyed +* @event +* @memberof Subscriber +*/ + +/** +* Dispatched when the video dimensions of the video change. This can occur when the +* stream.videoType property is set to "screen" (for a screen-sharing +* video stream), and the user resizes the window being captured. It can also occur if the video +* is being published by a mobile device and the user rotates the device (causing the camera +* orientation to change). +* @name videoDimensionsChanged +* @event +* @memberof Subscriber +*/ + +}; + +// tb_require('../helpers/helpers.js') +// tb_require('../helpers/lib/config.js') +// tb_require('./analytics.js') +// tb_require('./events.js') +// tb_require('./error_handling.js') +// tb_require('./system_requirements.js') +// tb_require('./stream.js') +// tb_require('./connection.js') +// tb_require('./environment_loader.js') +// tb_require('./session_info.js') +// tb_require('./messaging/raptor/raptor.js') +// tb_require('./messaging/raptor/session-dispatcher.js') +// tb_require('./qos_testing/webrtc_test.js') +// tb_require('./qos_testing/http_test.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT, Promise */ + + +/** + * The Session object returned by the OT.initSession() method provides access to + * much of the OpenTok functionality. + * + * @class Session + * @augments EventDispatcher + * + * @property {Capabilities} capabilities A {@link Capabilities} object that includes information + * about the capabilities of the client. All properties of the capabilities object + * are undefined until you have connected to a session and the Session object has dispatched the + * sessionConnected event. + * @property {Connection} connection The {@link Connection} object for this session. The + * connection property is only available once the Session object dispatches the sessionConnected + * event. The Session object asynchronously dispatches a sessionConnected event in response + * to a successful call to the connect() method. See: connect and + * {@link Connection}. + * @property {String} sessionId The session ID for this session. You pass this value into the + * OT.initSession() method when you create the Session object. (Note: a Session + * object is not connected to the OpenTok server until you call the connect() method of the + * object and the object dispatches a connected event. See {@link OT.initSession} and + * {@link connect}). + * For more information on sessions and session IDs, see + * Session creation. + */ +OT.Session = function(apiKey, sessionId) { + OT.$.eventing(this); + + // Check that the client meets the minimum requirements, if they don't the upgrade + // flow will be triggered. + if (!OT.checkSystemRequirements()) { + OT.upgradeSystemRequirements(); + return; + } + + if(sessionId == null) { + sessionId = apiKey; + apiKey = null; + } + + this.id = this.sessionId = sessionId; + + var _initialConnection = true, + _apiKey = apiKey, + _token, + _session = this, + _sessionId = sessionId, + _socket, + _widgetId = OT.$.uuid(), + _connectionId = OT.$.uuid(), + sessionConnectFailed, + sessionDisconnectedHandler, + connectionCreatedHandler, + connectionDestroyedHandler, + streamCreatedHandler, + streamPropertyModifiedHandler, + streamDestroyedHandler, + archiveCreatedHandler, + archiveDestroyedHandler, + archiveUpdatedHandler, + init, + reset, + disconnectComponents, + destroyPublishers, + destroySubscribers, + connectMessenger, + getSessionInfo, + onSessionInfoResponse, + permittedTo, + _connectivityAttemptPinger, + dispatchError; + + + + var setState = OT.$.statable(this, [ + 'disconnected', 'connecting', 'connected', 'disconnecting' + ], 'disconnected'); + + this.connection = null; + this.connections = new OT.$.Collection(); + this.streams = new OT.$.Collection(); + this.archives = new OT.$.Collection(); + + +//-------------------------------------- +// MESSAGE HANDLERS +//-------------------------------------- + +// The duplication of this and sessionConnectionFailed will go away when +// session and messenger are refactored + sessionConnectFailed = function(reason, code) { + setState('disconnected'); + + OT.error(reason); + + this.trigger('sessionConnectFailed', + new OT.Error(code || OT.ExceptionCodes.CONNECT_FAILED, reason)); + + OT.handleJsException(reason, code || OT.ExceptionCodes.CONNECT_FAILED, { + session: this + }); + }; + + sessionDisconnectedHandler = function(event) { + var reason = event.reason; + if(reason === 'networkTimedout') { + reason = 'networkDisconnected'; + this.logEvent('Connect', 'TimeOutDisconnect', {reason: event.reason}); + } else { + this.logEvent('Connect', 'Disconnected', {reason: event.reason}); + } + + var publicEvent = new OT.SessionDisconnectEvent('sessionDisconnected', reason); + + reset(); + disconnectComponents.call(this, reason); + + var defaultAction = OT.$.bind(function() { + // Publishers handle preventDefault'ing themselves + destroyPublishers.call(this, publicEvent.reason); + // Subscriers don't, destroy 'em if needed + if (!publicEvent.isDefaultPrevented()) destroySubscribers.call(this, publicEvent.reason); + }, this); + + this.dispatchEvent(publicEvent, defaultAction); + }; + + connectionCreatedHandler = function(connection) { + // We don't broadcast events for the symphony connection + if (connection.id.match(/^symphony\./)) return; + + this.dispatchEvent(new OT.ConnectionEvent( + OT.Event.names.CONNECTION_CREATED, + connection + )); + }; + + connectionDestroyedHandler = function(connection, reason) { + // We don't broadcast events for the symphony connection + if (connection.id.match(/^symphony\./)) return; + + // Don't delete the connection if it's ours. This only happens when + // we're about to receive a session disconnected and session disconnected + // will also clean up our connection. + if (connection.id === _socket.id()) return; + + this.dispatchEvent( + new OT.ConnectionEvent( + OT.Event.names.CONNECTION_DESTROYED, + connection, + reason + ) + ); + }; + + streamCreatedHandler = function(stream) { + if(stream.connection.id !== this.connection.id) { + this.dispatchEvent(new OT.StreamEvent( + OT.Event.names.STREAM_CREATED, + stream, + null, + false + )); + } + }; + + streamPropertyModifiedHandler = function(event) { + var stream = event.target, + propertyName = event.changedProperty, + newValue = event.newValue; + + if (propertyName === 'videoDisableWarning' || propertyName === 'audioDisableWarning') { + return; // These are not public properties, skip top level event for them. + } + + if (propertyName === 'orientation') { + propertyName = 'videoDimensions'; + newValue = {width: newValue.width, height: newValue.height}; + } + + this.dispatchEvent(new OT.StreamPropertyChangedEvent( + OT.Event.names.STREAM_PROPERTY_CHANGED, + stream, + propertyName, + event.oldValue, + newValue + )); + }; + + streamDestroyedHandler = function(stream, reason) { + + // if the stream is one of ours we delegate handling + // to the publisher itself. + if(stream.connection.id === this.connection.id) { + OT.$.forEach(OT.publishers.where({ streamId: stream.id }), OT.$.bind(function(publisher) { + publisher._.unpublishFromSession(this, reason); + }, this)); + return; + } + + var event = new OT.StreamEvent('streamDestroyed', stream, reason, true); + + var defaultAction = OT.$.bind(function() { + if (!event.isDefaultPrevented()) { + // If we are subscribed to any of the streams we should unsubscribe + OT.$.forEach(OT.subscribers.where({streamId: stream.id}), function(subscriber) { + if (subscriber.session.id === this.id) { + if(subscriber.stream) { + subscriber.destroy('streamDestroyed'); + } + } + }, this); + } else { + // @TODO Add a one time warning that this no longer cleans up the publisher + } + }, this); + + this.dispatchEvent(event, defaultAction); + }; + + archiveCreatedHandler = function(archive) { + this.dispatchEvent(new OT.ArchiveEvent('archiveStarted', archive)); + }; + + archiveDestroyedHandler = function(archive) { + this.dispatchEvent(new OT.ArchiveEvent('archiveDestroyed', archive)); + }; + + archiveUpdatedHandler = function(event) { + var archive = event.target, + propertyName = event.changedProperty, + newValue = event.newValue; + + if(propertyName === 'status' && newValue === 'stopped') { + this.dispatchEvent(new OT.ArchiveEvent('archiveStopped', archive)); + } else { + this.dispatchEvent(new OT.ArchiveEvent('archiveUpdated', archive)); + } + }; + + init = function() { + _session.token = _token = null; + setState('disconnected'); + _session.connection = null; + _session.capabilities = new OT.Capabilities([]); + _session.connections.destroy(); + _session.streams.destroy(); + _session.archives.destroy(); + }; + + // Put ourselves into a pristine state + reset = function() { + // reset connection id now so that following calls to testNetwork and connect will share + // the same new session id. We need to reset here because testNetwork might be call after + // and it is always called before the session is connected + // on initial connection we don't reset + _connectionId = OT.$.uuid(); + init(); + }; + + disconnectComponents = function(reason) { + OT.$.forEach(OT.publishers.where({session: this}), function(publisher) { + publisher.disconnect(reason); + }); + + OT.$.forEach(OT.subscribers.where({session: this}), function(subscriber) { + subscriber.disconnect(); + }); + }; + + destroyPublishers = function(reason) { + OT.$.forEach(OT.publishers.where({session: this}), function(publisher) { + publisher._.streamDestroyed(reason); + }); + }; + + destroySubscribers = function(reason) { + OT.$.forEach(OT.subscribers.where({session: this}), function(subscriber) { + subscriber.destroy(reason); + }); + }; + + connectMessenger = function() { + OT.debug('OT.Session: connecting to Raptor'); + + var socketUrl = this.sessionInfo.messagingURL, + symphonyUrl = OT.properties.symphonyAddresss || this.sessionInfo.symphonyAddress; + + _socket = new OT.Raptor.Socket(_connectionId, _widgetId, socketUrl, symphonyUrl, + OT.SessionDispatcher(this)); + + + _socket.connect(_token, this.sessionInfo, OT.$.bind(function(error, sessionState) { + if (error) { + _socket = void 0; + this.logConnectivityEvent('Failure', error); + + sessionConnectFailed.call(this, error.message, error.code); + return; + } + + OT.debug('OT.Session: Received session state from Raptor', sessionState); + + this.connection = this.connections.get(_socket.id()); + if(this.connection) { + this.capabilities = this.connection.permissions; + } + + setState('connected'); + + this.logConnectivityEvent('Success', null, {connectionId: this.connection.id}); + + // Listen for our own connection's destroyed event so we know when we've been disconnected. + this.connection.on('destroyed', sessionDisconnectedHandler, this); + + // Listen for connection updates + this.connections.on({ + add: connectionCreatedHandler, + remove: connectionDestroyedHandler + }, this); + + // Listen for stream updates + this.streams.on({ + add: streamCreatedHandler, + remove: streamDestroyedHandler, + update: streamPropertyModifiedHandler + }, this); + + this.archives.on({ + add: archiveCreatedHandler, + remove: archiveDestroyedHandler, + update: archiveUpdatedHandler + }, this); + + this.dispatchEvent( + new OT.SessionConnectEvent(OT.Event.names.SESSION_CONNECTED), OT.$.bind(function() { + this.connections._triggerAddEvents(); // { id: this.connection.id } + this.streams._triggerAddEvents(); // { id: this.stream.id } + this.archives._triggerAddEvents(); + }, this) + ); + + }, this)); + }; + + getSessionInfo = function() { + if (this.is('connecting')) { + OT.SessionInfo.get( + this, + OT.$.bind(onSessionInfoResponse, this), + OT.$.bind(function(error) { + sessionConnectFailed.call(this, error.message + + (error.code ? ' (' + error.code + ')' : ''), error.code); + }, this) + ); + } + }; + + onSessionInfoResponse = function(sessionInfo) { + if (this.is('connecting')) { + var overrides = OT.properties.sessionInfoOverrides; + this.sessionInfo = sessionInfo; + if (overrides != null && typeof overrides === 'object') { + this.sessionInfo = OT.$.defaults(overrides, this.sessionInfo); + } + if (this.sessionInfo.partnerId && this.sessionInfo.partnerId !== _apiKey) { + this.apiKey = _apiKey = this.sessionInfo.partnerId; + + var reason = 'Authentication Error: The API key does not match the token or session.'; + + var payload = { + code: OT.ExceptionCodes.AUTHENTICATION_ERROR, + message: reason + }; + this.logEvent('Failure', 'SessionInfo', payload); + + sessionConnectFailed.call(this, reason, OT.ExceptionCodes.AUTHENTICATION_ERROR); + } else { + connectMessenger.call(this); + } + } + }; + + // Check whether we have permissions to perform the action. + permittedTo = OT.$.bind(function(action) { + return this.capabilities.permittedTo(action); + }, this); + + dispatchError = OT.$.bind(function(code, message, completionHandler) { + OT.dispatchError(code, message, completionHandler, this); + }, this); + + this.logEvent = function(action, variation, payload, options) { + var event = { + action: action, + variation: variation, + payload: payload, + sessionId: _sessionId, + partnerId: _apiKey + }; + + event.connectionId = _connectionId; + + if (options) event = OT.$.extend(options, event); + OT.analytics.logEvent(event); + }; + + /** + * @typedef {Object} Stats + * @property {number} bytesSentPerSecond + * @property {number} bytesReceivedPerSecond + * @property {number} packetLossRatio + * @property {number} rtt + */ + + function getTestNetworkConfig(token) { + return new Promise(function(resolve, reject) { + OT.$.getJSON( + [OT.properties.apiURL, '/v2/partner/', _apiKey, '/session/', _sessionId, '/connection/', + _connectionId, '/testNetworkConfig'].join(''), + { + headers: {'X-TB-TOKEN-AUTH': token} + }, + function(errorEvent, response) { + if (errorEvent) { + var error = JSON.parse(errorEvent.target.responseText); + if (error.code === -1) { + reject(new OT.$.Error('Unexpected HTTP error codes ' + + errorEvent.target.status, '2001')); + } else if (error.code === 10001 || error.code === 10002) { + reject(new OT.$.Error(error.message, '1004')); + } else { + reject(new OT.$.Error(error.message, error.code)); + } + } else { + resolve(response); + } + }); + }); + } + + /** + * @param {string} token + * @param {OT.Publisher} publisher + * @param {function(?OT.$.Error, Stats=)} callback + */ + this.testNetwork = function(token, publisher, callback) { + + // intercept call to callback to log the result + var origCallback = callback; + callback = function loggingCallback(error, stats) { + if (error) { + _session.logEvent('testNetwork', 'Failure', { + failureCode: error.name || error.message || 'unknown' + }); + } else { + _session.logEvent('testNetwork', 'Success', stats); + } + origCallback(error, stats); + }; + + _session.logEvent('testNetwork', 'Attempt', {}); + + if(this.isConnected()) { + callback(new OT.$.Error('Session connected, cannot test network', 1015)); + return; + } + + var webRtcStreamPromise = new Promise( + function(resolve, reject) { + var webRtcStream = publisher._.webRtcStream(); + if (webRtcStream) { + resolve(webRtcStream); + } else { + + var onAccessAllowed = function() { + unbind(); + resolve(publisher._.webRtcStream()); + }; + + var onPublishComplete = function(error) { + if (error) { + unbind(); + reject(error); + } + }; + + var unbind = function() { + publisher.off('publishComplete', onPublishComplete); + publisher.off('accessAllowed', onAccessAllowed); + }; + + publisher.on('publishComplete', onPublishComplete); + publisher.on('accessAllowed', onAccessAllowed); + + } + }); + + var testConfig; + var webrtcStats; + Promise.all([getTestNetworkConfig(token), webRtcStreamPromise]) + .then(function(values) { + var webRtcStream = values[1]; + testConfig = values[0]; + return OT.webrtcTest({mediaConfig: testConfig.media, localStream: webRtcStream}); + }) + .then(function(stats) { + OT.debug('Received stats from webrtcTest: ', stats); + if (stats.bandwidth < testConfig.media.thresholdBitsPerSecond) { + return Promise.reject(new OT.$.Error('The detect bandwidth form the WebRTC stage of ' + + 'the test was not sufficient to run the HTTP stage of the test', 1553)); + } + + webrtcStats = stats; + }) + .then(function() { + return OT.httpTest({httpConfig: testConfig.http}); + }) + .then(function(httpStats) { + var stats = { + uploadBitsPerSecond: httpStats.uploadBandwidth, + downloadBitsPerSecond: httpStats.downloadBandwidth, + packetLossRatio: webrtcStats.packetLostRatio, + roundTripTimeMilliseconds: webrtcStats.roundTripTime + }; + callback(null, stats); + // IE8 (ES3 JS engine) requires bracket notation for "catch" keyword + })['catch'](function(error) { + callback(error); + }); + }; + + this.logConnectivityEvent = function(variation, payload, options) { + if (variation === 'Attempt' || !_connectivityAttemptPinger) { + var pingerOptions = { + action: 'Connect', + sessionId: _sessionId, + partnerId: _apiKey + }; + if (this.connection && this.connection.id) { + pingerOptions = event.connectionId = this.connection.id; + } else if (_connectionId) { + pingerOptions.connectionId = _connectionId; + } + _connectivityAttemptPinger = new OT.ConnectivityAttemptPinger(pingerOptions); + } + _connectivityAttemptPinger.setVariation(variation); + this.logEvent('Connect', variation, payload, options); + }; + +/** +* Connects to an OpenTok session. +*

+* Upon a successful connection, the completion handler (the second parameter of the method) is +* invoked without an error object passed in. (If there is an error connecting, the completion +* handler is invoked with an error object.) Make sure that you have successfully connected to the +* session before calling other methods of the Session object. +*

+*

+* The Session object dispatches a connectionCreated event when any client +* (including your own) connects to to the session. +*

+* +*
+* Example +*
+*

+* The following code initializes a session and sets up an event listener for when the session +* connects: +*

+*
+*  var apiKey = ""; // Replace with your API key. See https://dashboard.tokbox.com/projects
+*  var sessionID = ""; // Replace with your own session ID.
+*                      // See https://dashboard.tokbox.com/projects
+*  var token = ""; // Replace with a generated token that has been assigned the moderator role.
+*                  // See https://dashboard.tokbox.com/projects
+*
+*  var session = OT.initSession(apiKey, sessionID);
+*  session.on("sessionConnected", function(sessionConnectEvent) {
+*      //
+*  });
+*  session.connect(token);
+*  
+*

+*

+* In this example, the sessionConnectHandler() function is passed an event +* object of type {@link SessionConnectEvent}. +*

+* +*
+* Events dispatched: +*
+* +*

+* exception (ExceptionEvent) — Dispatched +* by the OT class locally in the event of an error. +*

+*

+* connectionCreated (ConnectionEvent) — +* Dispatched by the Session object on all clients connected to the session. +*

+*

+* sessionConnected (SessionConnectEvent) +* — Dispatched locally by the Session object when the connection is established. +*

+* +* @param {String} token The session token. You generate a session token using our +* server-side libraries or the +* Dashboard page. For more information, see +* Connection token creation. +* +* @param {Function} completionHandler (Optional) A function to be called when the call to the +* connect() method succeeds or fails. This function takes one parameter — +* error (see the Error object). +* On success, the completionHandler function is not passed any +* arguments. On error, the function is passed an error object parameter +* (see the Error object). The +* error object has two properties: code (an integer) and +* message (a string), which identify the cause of the failure. The following +* code adds a completionHandler when calling the connect() method: +*
+* session.connect(token, function (error) {
+*   if (error) {
+*       console.log(error.message);
+*   } else {
+*     console.log("Connected to session.");
+*   }
+* });
+* 
+*

+* Note that upon connecting to the session, the Session object dispatches a +* sessionConnected event in addition to calling the completionHandler. +* The SessionConnectEvent object, which defines the sessionConnected event, +* includes connections and streams properties, which +* list the connections and streams in the session when you connect. +*

+* +* @see SessionConnectEvent +* @method #connect +* @memberOf Session +*/ + this.connect = function(token) { + + if(apiKey == null && arguments.length > 1 && + (typeof arguments[0] === 'string' || typeof arguments[0] === 'number') && + typeof arguments[1] === 'string') { + _apiKey = token.toString(); + token = arguments[1]; + } + + // The completion handler is always the last argument. + var completionHandler = arguments[arguments.length - 1]; + + if (this.is('connecting', 'connected')) { + OT.warn('OT.Session: Cannot connect, the session is already ' + this.state); + return this; + } + + init(); + setState('connecting'); + this.token = _token = !OT.$.isFunction(token) && token; + + // Get a new widget ID when reconnecting. + if (_initialConnection) { + _initialConnection = false; + } else { + _widgetId = OT.$.uuid(); + } + + if (completionHandler && OT.$.isFunction(completionHandler)) { + this.once('sessionConnected', OT.$.bind(completionHandler, null, null)); + this.once('sessionConnectFailed', completionHandler); + } + + if(_apiKey == null || OT.$.isFunction(_apiKey)) { + setTimeout(OT.$.bind( + sessionConnectFailed, + this, + 'API Key is undefined. You must pass an API Key to initSession.', + OT.ExceptionCodes.AUTHENTICATION_ERROR + )); + + return this; + } + + if (!_sessionId) { + setTimeout(OT.$.bind( + sessionConnectFailed, + this, + 'SessionID is undefined. You must pass a sessionID to initSession.', + OT.ExceptionCodes.INVALID_SESSION_ID + )); + + return this; + } + + this.apiKey = _apiKey = _apiKey.toString(); + + // Ugly hack, make sure OT.APIKEY is set + if (OT.APIKEY.length === 0) { + OT.APIKEY = _apiKey; + } + + this.logConnectivityEvent('Attempt'); + + getSessionInfo.call(this); + return this; + }; + +/** +* Disconnects from the OpenTok session. +* +*

+* Calling the disconnect() method ends your connection with the session. In the +* course of terminating your connection, it also ceases publishing any stream(s) you were +* publishing. +*

+*

+* Session objects on remote clients dispatch streamDestroyed events for any +* stream you were publishing. The Session object dispatches a sessionDisconnected +* event locally. The Session objects on remote clients dispatch connectionDestroyed +* events, letting other connections know you have left the session. The +* {@link SessionDisconnectEvent} and {@link StreamEvent} objects that define the +* sessionDisconnect and connectionDestroyed events each have a +* reason property. The reason property lets the developer determine +* whether the connection is being terminated voluntarily and whether any streams are being +* destroyed as a byproduct of the underlying connection's voluntary destruction. +*

+*

+* If the session is not currently connected, calling this method causes a warning to be logged. +* See OT.setLogLevel(). +*

+* +*

+* Note: If you intend to reuse a Publisher object created using +* OT.initPublisher() to publish to different sessions sequentially, call either +* Session.disconnect() or Session.unpublish(). Do not call both. +* Then call the preventDefault() method of the streamDestroyed or +* sessionDisconnected event object to prevent the Publisher object from being +* removed from the page. Be sure to call preventDefault() only if the +* connection.connectionId property of the Stream object in the event matches the +* connection.connectionId property of your Session object (to ensure that you are +* preventing the default behavior for your published streams, not for other streams that you +* subscribe to). +*

+* +*
+* Events dispatched: +*
+*

+* sessionDisconnected +* (SessionDisconnectEvent) +* — Dispatched locally when the connection is disconnected. +*

+*

+* connectionDestroyed (ConnectionEvent) — +* Dispatched on other clients, along with the streamDestroyed event (as warranted). +*

+* +*

+* streamDestroyed (StreamEvent) — +* Dispatched on other clients if streams are lost as a result of the session disconnecting. +*

+* +* @method #disconnect +* @memberOf Session +*/ + var disconnect = OT.$.bind(function disconnect(drainSocketBuffer) { + if (_socket && _socket.isNot('disconnected')) { + if (_socket.isNot('disconnecting')) { + setState('disconnecting'); + _socket.disconnect(drainSocketBuffer); + } + } + else { + reset(); + } + }, this); + + this.disconnect = function(drainSocketBuffer) { + disconnect(drainSocketBuffer !== void 0 ? drainSocketBuffer : true); + }; + + this.destroy = function(reason) { + this.streams.destroy(); + this.connections.destroy(); + this.archives.destroy(); + disconnect(reason !== 'unloaded'); + }; + +/** +* The publish() method starts publishing an audio-video stream to the session. +* The audio-video stream is captured from a local microphone and webcam. Upon successful +* publishing, the Session objects on all connected clients dispatch the +* streamCreated event. +*

+* +* +*

You pass a Publisher object as the one parameter of the method. You can initialize a +* Publisher object by calling the OT.initPublisher() +* method. Before calling Session.publish(). +*

+* +*

This method takes an alternate form: publish([targetElement:String, +* properties:Object]):Publisher — In this form, you do not pass a Publisher +* object into the function. Instead, you pass in a targetElement (the ID of the +* DOM element that the Publisher will replace) and a properties object that +* defines options for the Publisher (see OT.initPublisher().) +* The method returns a new Publisher object, which starts sending an audio-video stream to the +* session. The remainder of this documentation describes the form that takes a single Publisher +* object as a parameter. +* +*

+* A local display of the published stream is created on the web page by replacing +* the specified element in the DOM with a streaming video display. The video stream +* is automatically mirrored horizontally so that users see themselves and movement +* in their stream in a natural way. If the width and height of the display do not match +* the 4:3 aspect ratio of the video signal, the video stream is cropped to fit the +* display. +*

+* +*

+* If calling this method creates a new Publisher object and the OpenTok library does not +* have access to the camera or microphone, the web page alerts the user to grant access +* to the camera and microphone. +*

+* +*

+* The OT object dispatches an exception event if the user's role does not +* include permissions required to publish. For example, if the user's role is set to subscriber, +* then they cannot publish. You define a user's role when you create the user token using the +* generate_token() method of the +* OpenTok server-side +* libraries or the Dashboard page. +* You pass the token string as a parameter of the connect() method of the Session +* object. See ExceptionEvent and +* OT.on(). +*

+*

+* The application throws an error if the session is not connected. +*

+* +*
Events dispatched:
+*

+* exception (ExceptionEvent) — Dispatched +* by the OT object. This can occur when user's role does not allow publishing (the +* code property of event object is set to 1500); it can also occur if the c +* onnection fails to connect (the code property of event object is set to 1013). +* WebRTC is a peer-to-peer protocol, and it is possible that connections will fail to connect. +* The most common cause for failure is a firewall that the protocol cannot traverse. +*

+*

+* streamCreated (StreamEvent) — +* The stream has been published. The Session object dispatches this on all clients +* subscribed to the stream, as well as on the publisher's client. +*

+* +*
Example
+* +*

+* The following example publishes a video once the session connects: +*

+*
+* var sessionId = ""; // Replace with your own session ID.
+*                     // See https://dashboard.tokbox.com/projects
+* var token = ""; // Replace with a generated token that has been assigned the moderator role.
+*                 // See https://dashboard.tokbox.com/projects
+* var session = OT.initSession(apiKey, sessionID);
+* session.on("sessionConnected", function (event) {
+*     var publisherOptions = {width: 400, height:300, name:"Bob's stream"};
+*     // This assumes that there is a DOM element with the ID 'publisher':
+*     publisher = OT.initPublisher('publisher', publisherOptions);
+*     session.publish(publisher);
+* });
+* session.connect(token);
+* 
+* +* @param {Publisher} publisher A Publisher object, which you initialize by calling the +* OT.initPublisher() method. +* +* @param {Function} completionHandler (Optional) A function to be called when the call to the +* publish() method succeeds or fails. This function takes one parameter — +* error. On success, the completionHandler function is not passed any +* arguments. On error, the function is passed an error object parameter +* (see the Error object). The +* error object has two properties: code (an integer) and +* message (a string), which identify the cause of the failure. Calling +* publish() fails if the role assigned to your token is not "publisher" or +* "moderator"; in this case error.code is set to 1500. Calling +* publish() also fails the client fails to connect; in this case +* error.code is set to 1013. The following code adds a +* completionHandler when calling the publish() method: +*
+* session.publish(publisher, null, function (error) {
+*   if (error) {
+*     console.log(error.message);
+*   } else {
+*     console.log("Publishing a stream.");
+*   }
+* });
+* 
+* +* @returns The Publisher object for this stream. +* +* @method #publish +* @memberOf Session +*/ + this.publish = function(publisher, properties, completionHandler) { + if(typeof publisher === 'function') { + completionHandler = publisher; + publisher = undefined; + } + if(typeof properties === 'function') { + completionHandler = properties; + properties = undefined; + } + if (this.isNot('connected')) { + OT.analytics.logError(1010, 'OT.exception', + 'We need to be connected before you can publish', null, { + action: 'Publish', + variation: 'Failure', + payload: { + reason:'unconnected', + code: OT.ExceptionCodes.NOT_CONNECTED, + message: 'We need to be connected before you can publish' + }, + sessionId: _sessionId, + partnerId: _apiKey, + }); + + if (completionHandler && OT.$.isFunction(completionHandler)) { + dispatchError(OT.ExceptionCodes.NOT_CONNECTED, + 'We need to be connected before you can publish', completionHandler); + } + + return null; + } + + if (!permittedTo('publish')) { + var errorMessage = 'This token does not allow publishing. The role must be at least ' + + '`publisher` to enable this functionality'; + var payload = { + reason: 'permission', + code: OT.ExceptionCodes.UNABLE_TO_PUBLISH, + message: errorMessage + }; + this.logEvent('publish', 'Failure', payload); + dispatchError(OT.ExceptionCodes.UNABLE_TO_PUBLISH, errorMessage, completionHandler); + return null; + } + + // If the user has passed in an ID of a element then we create a new publisher. + if (!publisher || typeof(publisher)==='string' || OT.$.isElementNode(publisher)) { + // Initiate a new Publisher with the new session credentials + publisher = OT.initPublisher(publisher, properties); + + } else if (publisher instanceof OT.Publisher){ + + // If the publisher already has a session attached to it we can + if ('session' in publisher && publisher.session && 'sessionId' in publisher.session) { + // send a warning message that we can't publish again. + if( publisher.session.sessionId === this.sessionId){ + OT.warn('Cannot publish ' + publisher.guid() + ' again to ' + + this.sessionId + '. Please call session.unpublish(publisher) first.'); + } else { + OT.warn('Cannot publish ' + publisher.guid() + ' publisher already attached to ' + + publisher.session.sessionId+ '. Please call session.unpublish(publisher) first.'); + } + } + + } else { + dispatchError(OT.ExceptionCodes.UNABLE_TO_PUBLISH, + 'Session.publish :: First parameter passed in is neither a ' + + 'string nor an instance of the Publisher', + completionHandler); + return; + } + + publisher.once('publishComplete', function(err) { + if (err) { + dispatchError(OT.ExceptionCodes.UNABLE_TO_PUBLISH, + 'Session.publish :: ' + err.message, + completionHandler); + return; + } + + if (completionHandler && OT.$.isFunction(completionHandler)) { + completionHandler.apply(null, arguments); + } + }); + + // Add publisher reference to the session + publisher._.publishToSession(this); + + // return the embed publisher + return publisher; + }; + +/** +* Ceases publishing the specified publisher's audio-video stream +* to the session. By default, the local representation of the audio-video stream is +* removed from the web page. Upon successful termination, the Session object on every +* connected web page dispatches +* a streamDestroyed event. +*

+* +*

+* To prevent the Publisher from being removed from the DOM, add an event listener for the +* streamDestroyed event dispatched by the Publisher object and call the +* preventDefault() method of the event object. +*

+* +*

+* Note: If you intend to reuse a Publisher object created using +* OT.initPublisher() to publish to different sessions sequentially, call +* either Session.disconnect() or Session.unpublish(). Do not call +* both. Then call the preventDefault() method of the streamDestroyed +* or sessionDisconnected event object to prevent the Publisher object from being +* removed from the page. Be sure to call preventDefault() only if the +* connection.connectionId property of the Stream object in the event matches the +* connection.connectionId property of your Session object (to ensure that you are +* preventing the default behavior for your published streams, not for other streams that you +* subscribe to). +*

+* +*
Events dispatched:
+* +*

+* streamDestroyed (StreamEvent) — +* The stream associated with the Publisher has been destroyed. Dispatched on by the +* Publisher on on the Publisher's browser. Dispatched by the Session object on +* all other connections subscribing to the publisher's stream. +*

+* +*
Example
+* +* The following example publishes a stream to a session and adds a Disconnect link to the +* web page. Clicking this link causes the stream to stop being published. +* +*
+* <script>
+*     var apiKey = ""; // Replace with your API key. See https://dashboard.tokbox.com/projects
+*     var sessionID = ""; // Replace with your own session ID.
+*                      // See https://dashboard.tokbox.com/projects
+*     var token = "Replace with the TokBox token string provided to you."
+*     var session = OT.initSession(apiKey, sessionID);
+*     session.on("sessionConnected", function sessionConnectHandler(event) {
+*         // This assumes that there is a DOM element with the ID 'publisher':
+*         publisher = OT.initPublisher('publisher');
+*         session.publish(publisher);
+*     });
+*     session.connect(token);
+*     var publisher;
+*
+*     function unpublish() {
+*         session.unpublish(publisher);
+*     }
+* </script>
+*
+* <body>
+*
+*     <div id="publisherContainer/>
+*     <br/>
+*
+*     <a href="javascript:unpublish()">Stop Publishing</a>
+*
+* </body>
+*
+* 
+* +* @see publish() +* +* @see streamDestroyed event +* +* @param {Publisher} publisher The Publisher object to stop streaming. +* +* @method #unpublish +* @memberOf Session +*/ + this.unpublish = function(publisher) { + if (!publisher) { + OT.error('OT.Session.unpublish: publisher parameter missing.'); + return; + } + + // Unpublish the localMedia publisher + publisher._.unpublishFromSession(this, 'unpublished'); + }; + + +/** +* Subscribes to a stream that is available to the session. You can get an array of +* available streams from the streams property of the sessionConnected +* and streamCreated events (see +* SessionConnectEvent and +* StreamEvent). +*

+*

+* The subscribed stream is displayed on the local web page by replacing the specified element +* in the DOM with a streaming video display. If the width and height of the display do not +* match the 4:3 aspect ratio of the video signal, the video stream is cropped to fit +* the display. If the stream lacks a video component, a blank screen with an audio indicator +* is displayed in place of the video stream. +*

+* +*

+* The application throws an error if the session is not connected or if the +* targetElement does not exist in the HTML DOM. +*

+* +*
Example
+* +* The following code subscribes to other clients' streams: +* +*
+* var apiKey = ""; // Replace with your API key. See https://dashboard.tokbox.com/projects
+* var sessionID = ""; // Replace with your own session ID.
+*                     // See https://dashboard.tokbox.com/projects
+*
+* var session = OT.initSession(apiKey, sessionID);
+* session.on("streamCreated", function(event) {
+*   subscriber = session.subscribe(event.stream, targetElement);
+* });
+* session.connect(token);
+* 
+* +* @param {Stream} stream The Stream object representing the stream to which we are trying to +* subscribe. +* +* @param {Object} targetElement (Optional) The DOM element or the id attribute of +* the existing DOM element used to determine the location of the Subscriber video in the HTML +* DOM. See the insertMode property of the properties parameter. If +* you do not specify a targetElement, the application appends a new DOM element +* to the HTML body. +* +* @param {Object} properties This is an object that contains the following properties: +*
    +*
  • audioVolume (Number) — The desired audio volume, between 0 and +* 100, when the Subscriber is first opened (default: 50). After you subscribe to the +* stream, you can adjust the volume by calling the +* setAudioVolume() method of the +* Subscriber object. This volume setting affects local playback only; it does not affect +* the stream's volume on other clients.
  • +* +*
  • +* fitMode (String) — Determines how the video is displayed if the its +* dimensions do not match those of the DOM element. You can set this property to one of +* the following values: +*

    +*

      +*
    • +* "cover" — The video is cropped if its dimensions do not match +* those of the DOM element. This is the default setting for screen-sharing videos +* (for Stream objects with the videoType property set to +* "screen"). +*
    • +*
    • +* "contain" — The video is letter-boxed if its dimensions do not +* match those of the DOM element. This is the default setting for videos that have a +* camera as the source (for Stream objects with the videoType property +* set to "camera"). +*
    • +*
    +*
  • +* +*
  • height (Number) — The desired height, in pixels, of the +* displayed Subscriber video stream (default: 198). Note: Use the +* height and width properties to set the dimensions +* of the Subscriber video; do not set the height and width of the DOM element +* (using CSS).
  • +* +*
  • +* insertMode (String) — Specifies how the Subscriber object will +* be inserted in the HTML DOM. See the targetElement parameter. This +* string can have the following values: +*
      +*
    • "replace" — The Subscriber object replaces contents of the +* targetElement. This is the default.
    • +*
    • "after" — The Subscriber object is a new element inserted +* after the targetElement in the HTML DOM. (Both the Subscriber and targetElement +* have the same parent element.)
    • +*
    • "before" — The Subscriber object is a new element inserted +* before the targetElement in the HTML DOM. (Both the Subsciber and targetElement +* have the same parent element.)
    • +*
    • "append" — The Subscriber object is a new element added as a +* child of the targetElement. If there are other child elements, the Subscriber is +* appended as the last child element of the targetElement.
    • +*
    +*
  • +* +*
  • +* style (Object) — An object containing properties that define the initial +* appearance of user interface controls of the Subscriber. The style object +* includes the following properties: +*
      +*
    • audioLevelDisplayMode (String) — How to display the audio level +* indicator. Possible values are: "auto" (the indicator is displayed when the +* video is disabled), "off" (the indicator is not displayed), and +* "on" (the indicator is always displayed).
    • +* +*
    • backgroundImageURI (String) — A URI for an image to display as +* the background image when a video is not displayed. (A video may not be displayed if +* you call subscribeToVideo(false) on the Subscriber object). You can pass an +* http or https URI to a PNG, JPEG, or non-animated GIF file location. You can also use the +* data URI scheme (instead of http or https) and pass in base-64-encrypted +* PNG data, such as that obtained from the +* Subscriber.getImgData() method. For example, +* you could set the property to "data:VBORw0KGgoAA...", where the portion of +* the string after "data:" is the result of a call to +* Subscriber.getImgData(). If the URL or the image data is invalid, the +* property is ignored (the attempt to set the image fails silently). +*

      +* Note that in Internet Explorer 8 (using the OpenTok Plugin for Internet Explorer), +* you cannot set the backgroundImageURI style to a string larger than +* 32 kB. This is due to an IE 8 limitation on the size of URI strings. Due to this +* limitation, you cannot set the backgroundImageURI style to a string obtained +* with the getImgData() method. +*

    • +* +*
    • buttonDisplayMode (String) — How to display the speaker controls +* Possible values are: "auto" (controls are displayed when the stream is first +* displayed and when the user mouses over the display), "off" (controls are not +* displayed), and "on" (controls are always displayed).
    • +* +*
    • nameDisplayMode (String) — Whether to display the stream name. +* Possible values are: "auto" (the name is displayed when the stream is first +* displayed and when the user mouses over the display), "off" (the name is not +* displayed), and "on" (the name is always displayed).
    • +* +*
    • videoDisabledDisplayMode (String) — Whether to display the video +* disabled indicator and video disabled warning icons for a Subscriber. These icons +* indicate that the video has been disabled (or is in risk of being disabled for +* the warning icon) due to poor stream quality. This style only applies to the Subscriber +* object. Possible values are: "auto" (the icons are automatically when the +* displayed video is disabled or in risk of being disabled due to poor stream quality), +* "off" (do not display the icons), and "on" (display the +* icons). The default setting is "auto"
    • +*
    +*
  • +* +*
  • subscribeToAudio (Boolean) — Whether to initially subscribe to audio +* (if available) for the stream (default: true).
  • +* +*
  • subscribeToVideo (Boolean) — Whether to initially subscribe to video +* (if available) for the stream (default: true).
  • +* +*
  • width (Number) — The desired width, in pixels, of the +* displayed Subscriber video stream (default: 264). Note: Use the +* height and width properties to set the dimensions +* of the Subscriber video; do not set the height and width of the DOM element +* (using CSS).
  • +* +*
+* +* @param {Function} completionHandler (Optional) A function to be called when the call to the +* subscribe() method succeeds or fails. This function takes one parameter — +* error. On success, the completionHandler function is not passed any +* arguments. On error, the function is passed an error object, defined by the +* Error class, has two properties: code (an integer) and +* message (a string), which identify the cause of the failure. The following +* code adds a completionHandler when calling the subscribe() method: +*
+* session.subscribe(stream, "subscriber", null, function (error) {
+*   if (error) {
+*     console.log(error.message);
+*   } else {
+*     console.log("Subscribed to stream: " + stream.id);
+*   }
+* });
+* 
+* +* @signature subscribe(stream, targetElement, properties, completionHandler) +* @returns {Subscriber} The Subscriber object for this stream. Stream control functions +* are exposed through the Subscriber object. +* @method #subscribe +* @memberOf Session +*/ + this.subscribe = function(stream, targetElement, properties, completionHandler) { + + if (!this.connection || !this.connection.connectionId) { + dispatchError(OT.ExceptionCodes.UNABLE_TO_SUBSCRIBE, + 'Session.subscribe :: Connection required to subscribe', + completionHandler); + return; + } + + if (!stream) { + dispatchError(OT.ExceptionCodes.UNABLE_TO_SUBSCRIBE, + 'Session.subscribe :: stream cannot be null', + completionHandler); + return; + } + + if (!stream.hasOwnProperty('streamId')) { + dispatchError(OT.ExceptionCodes.UNABLE_TO_SUBSCRIBE, + 'Session.subscribe :: invalid stream object', + completionHandler); + return; + } + + if(typeof targetElement === 'function') { + completionHandler = targetElement; + targetElement = undefined; + properties = undefined; + } + + if(typeof properties === 'function') { + completionHandler = properties; + properties = undefined; + } + + var subscriber = new OT.Subscriber(targetElement, OT.$.extend(properties || {}, { + stream: stream, + session: this + }), function(err) { + + if (err) { + dispatchError(OT.ExceptionCodes.UNABLE_TO_SUBSCRIBE, + 'Session.subscribe :: ' + err.message, + completionHandler); + + } else if (completionHandler && OT.$.isFunction(completionHandler)) { + completionHandler.apply(null, arguments); + } + + }); + + OT.subscribers.add(subscriber); + + return subscriber; + + }; + +/** +* Stops subscribing to a stream in the session. the display of the audio-video stream is +* removed from the local web page. +* +*
Example
+*

+* The following code subscribes to other clients' streams. For each stream, the code also +* adds an Unsubscribe link. +*

+*
+* var apiKey = ""; // Replace with your API key. See https://dashboard.tokbox.com/projects
+* var sessionID = ""; // Replace with your own session ID.
+*                     // See https://dashboard.tokbox.com/projects
+* var streams = [];
+*
+* var session = OT.initSession(apiKey, sessionID);
+* session.on("streamCreated", function(event) {
+*     var stream = event.stream;
+*     displayStream(stream);
+* });
+* session.connect(token);
+*
+* function displayStream(stream) {
+*     var div = document.createElement('div');
+*     div.setAttribute('id', 'stream' + stream.streamId);
+*
+*     var subscriber = session.subscribe(stream, div);
+*     subscribers.push(subscriber);
+*
+*     var aLink = document.createElement('a');
+*     aLink.setAttribute('href', 'javascript: unsubscribe("' + subscriber.id + '")');
+*     aLink.innerHTML = "Unsubscribe";
+*
+*     var streamsContainer = document.getElementById('streamsContainer');
+*     streamsContainer.appendChild(div);
+*     streamsContainer.appendChild(aLink);
+*
+*     streams = event.streams;
+* }
+*
+* function unsubscribe(subscriberId) {
+*     console.log("unsubscribe called");
+*     for (var i = 0; i < subscribers.length; i++) {
+*         var subscriber = subscribers[i];
+*         if (subscriber.id == subscriberId) {
+*             session.unsubscribe(subscriber);
+*         }
+*     }
+* }
+* 
+* +* @param {Subscriber} subscriber The Subscriber object to unsubcribe. +* +* @see subscribe() +* +* @method #unsubscribe +* @memberOf Session +*/ + this.unsubscribe = function(subscriber) { + if (!subscriber) { + var errorMsg = 'OT.Session.unsubscribe: subscriber cannot be null'; + OT.error(errorMsg); + throw new Error(errorMsg); + } + + if (!subscriber.stream) { + OT.warn('OT.Session.unsubscribe:: tried to unsubscribe a subscriber that had no stream'); + return false; + } + + OT.debug('OT.Session.unsubscribe: subscriber ' + subscriber.id); + + subscriber.destroy(); + + return true; + }; + +/** +* Returns an array of local Subscriber objects for a given stream. +* +* @param {Stream} stream The stream for which you want to find subscribers. +* +* @returns {Array} An array of {@link Subscriber} objects for the specified stream. +* +* @see unsubscribe() +* @see Subscriber +* @see StreamEvent +* @method #getSubscribersForStream +* @memberOf Session +*/ + this.getSubscribersForStream = function(stream) { + return OT.subscribers.where({streamId: stream.id}); + }; + +/** +* Returns the local Publisher object for a given stream. +* +* @param {Stream} stream The stream for which you want to find the Publisher. +* +* @returns {Publisher} A Publisher object for the specified stream. Returns +* null if there is no local Publisher object +* for the specified stream. +* +* @see forceUnpublish() +* @see Subscriber +* @see StreamEvent +* +* @method #getPublisherForStream +* @memberOf Session +*/ + this.getPublisherForStream = function(stream) { + var streamId, + errorMsg; + + if (typeof stream === 'string') { + streamId = stream; + } else if (typeof stream === 'object' && stream && stream.hasOwnProperty('id')) { + streamId = stream.id; + } else { + errorMsg = 'Session.getPublisherForStream :: Invalid stream type'; + OT.error(errorMsg); + throw new Error(errorMsg); + } + + return OT.publishers.where({streamId: streamId})[0]; + }; + + // Private Session API: for internal OT use only + this._ = { + jsepCandidateP2p: function(streamId, subscriberId, candidate) { + return _socket.jsepCandidateP2p(streamId, subscriberId, candidate); }, - initialState = 'NotPublishing'; + jsepCandidate: function(streamId, candidate) { + return _socket.jsepCandidate(streamId, candidate); + }, - OT.PublishingState = OT.generateSimpleStateMachine(initialState, validStates, validTransitions); + jsepOffer: function(streamId, offerSdp) { + return _socket.jsepOffer(streamId, offerSdp); + }, - OT.PublishingState.prototype.isDestroyed = function() { - return this.current === 'Destroyed'; + jsepOfferP2p: function(streamId, subscriberId, offerSdp) { + return _socket.jsepOfferP2p(streamId, subscriberId, offerSdp); + }, + + jsepAnswer: function(streamId, answerSdp) { + return _socket.jsepAnswer(streamId, answerSdp); + }, + + jsepAnswerP2p: function(streamId, subscriberId, answerSdp) { + return _socket.jsepAnswerP2p(streamId, subscriberId, answerSdp); + }, + + // session.on("signal", function(SignalEvent)) + // session.on("signal:{type}", function(SignalEvent)) + dispatchSignal: OT.$.bind(function(fromConnection, type, data) { + var event = new OT.SignalEvent(type, data, fromConnection); + event.target = this; + + // signal a "signal" event + // NOTE: trigger doesn't support defaultAction, and therefore preventDefault. + this.trigger(OT.Event.names.SIGNAL, event); + + // signal an "signal:{type}" event" if there was a custom type + if (type) this.dispatchEvent(event); + }, this), + + subscriberCreate: function(stream, subscriber, channelsToSubscribeTo, completion) { + return _socket.subscriberCreate(stream.id, subscriber.widgetId, + channelsToSubscribeTo, completion); + }, + + subscriberDestroy: function(stream, subscriber) { + return _socket.subscriberDestroy(stream.id, subscriber.widgetId); + }, + + subscriberUpdate: function(stream, subscriber, attributes) { + return _socket.subscriberUpdate(stream.id, subscriber.widgetId, attributes); + }, + + subscriberChannelUpdate: function(stream, subscriber, channel, attributes) { + return _socket.subscriberChannelUpdate(stream.id, subscriber.widgetId, channel.id, + attributes); + }, + + streamCreate: function(name, audioFallbackEnabled, channels, completion) { + _socket.streamCreate( + name, + audioFallbackEnabled, + channels, + OT.Config.get('bitrates', 'min', OT.APIKEY), + OT.Config.get('bitrates', 'max', OT.APIKEY), + completion + ); + }, + + streamDestroy: function(streamId) { + _socket.streamDestroy(streamId); + }, + + streamChannelUpdate: function(stream, channel, attributes) { + _socket.streamChannelUpdate(stream.id, channel.id, attributes); + } }; - OT.PublishingState.prototype.isAttemptingToPublish = function() { - return OT.$.arrayIndexOf( - [ 'GetUserMedia', 'BindingMedia', 'MediaBound', 'PublishingToSession' ], - this.current) !== -1; + +/** +* Sends a signal to each client or a specified client in the session. Specify a +* to property of the signal parameter to limit the signal to +* be sent to a specific client; otherwise the signal is sent to each client connected to +* the session. +*

+* The following example sends a signal of type "foo" with a specified data payload ("hello") +* to all clients connected to the session: +*

+* session.signal({
+*     type: "foo",
+*     data: "hello"
+*   },
+*   function(error) {
+*     if (error) {
+*       console.log("signal error: " + error.message);
+*     } else {
+*       console.log("signal sent");
+*     }
+*   }
+* );
+* 
+*

+* Calling this method without specifying a recipient client (by setting the to +* property of the signal parameter) results in multiple signals sent (one to each +* client in the session). For information on charges for signaling, see the +* OpenTok pricing page. +*

+* The following example sends a signal of type "foo" with a data payload ("hello") to a +* specific client connected to the session: +*

+* session.signal({
+*     type: "foo",
+*     to: recipientConnection; // a Connection object
+*     data: "hello"
+*   },
+*   function(error) {
+*     if (error) {
+*       console.log("signal error: " + error.message);
+*     } else {
+*       console.log("signal sent");
+*     }
+*   }
+* );
+* 
+*

+* Add an event handler for the signal event to listen for all signals sent in +* the session. Add an event handler for the signal:type event to listen for +* signals of a specified type only (replace type, in signal:type, +* with the type of signal to listen for). The Session object dispatches these events. (See +* events.) +* +* @param {Object} signal An object that contains the following properties defining the signal: +*

    +*
  • data — (String) The data to send. The limit to the length of data +* string is 8kB. Do not set the data string to null or +* undefined.
  • +*
  • to — (Connection) A Connection +* object corresponding to the client that the message is to be sent to. If you do not +* specify this property, the signal is sent to all clients connected to the session.
  • +*
  • type — (String) The type of the signal. You can use the type to +* filter signals when setting an event handler for the signal:type event +* (where you replace type with the type string). The maximum length of the +* type string is 128 characters, and it must contain only letters (A-Z and a-z), +* numbers (0-9), '-', '_', and '~'.
  • +* +*
+* +*

Each property is optional. If you set none of the properties, you will send a signal +* with no data or type to each client connected to the session.

+* +* @param {Function} completionHandler A function that is called when sending the signal +* succeeds or fails. This function takes one parameter — error. +* On success, the completionHandler function is not passed any +* arguments. On error, the function is passed an error object, defined by the +* Error class. The error object has the following +* properties: +* +*
    +*
  • code — (Number) An error code, which can be one of the following: +* +* +* +* +* +* +* +* +* +* +* +* +* +*
    400 One of the signal properties — data, type, or to — +* is invalid.
    404 The client specified by the to property is not connected to +* the session.
    413 The type string exceeds the maximum length (128 bytes), +* or the data string exceeds the maximum size (8 kB).
    500 You are not connected to the OpenTok session.
    +*
  • +*
  • message — (String) A description of the error.
  • +*
+* +*

Note that the completionHandler success result (error == null) +* indicates that the options passed into the Session.signal() method are valid +* and the signal was sent. It does not indicate that the signal was successfully +* received by any of the intended recipients. +* +* @method #signal +* @memberOf Session +* @see signal and signal:type events +*/ + this.signal = function(options, completion) { + var _options = options, + _completion = completion; + + if (OT.$.isFunction(_options)) { + _completion = _options; + _options = null; + } + + if (this.isNot('connected')) { + var notConnectedErrorMsg = 'Unable to send signal - you are not connected to the session.'; + dispatchError(500, notConnectedErrorMsg, _completion); + return; + } + + _socket.signal(_options, _completion, this.logEvent); + if (options && options.data && (typeof(options.data) !== 'string')) { + OT.warn('Signaling of anything other than Strings is deprecated. ' + + 'Please update the data property to be a string.'); + } }; - OT.PublishingState.prototype.isPublishing = function() { - return this.current === 'Publishing'; +/** +* Forces a remote connection to leave the session. +* +*

+* The forceDisconnect() method is normally used as a moderation tool +* to remove users from an ongoing session. +*

+*

+* When a connection is terminated using the forceDisconnect(), +* sessionDisconnected, connectionDestroyed and +* streamDestroyed events are dispatched in the same way as they +* would be if the connection had terminated itself using the disconnect() +* method. However, the reason property of a {@link ConnectionEvent} or +* {@link StreamEvent} object specifies "forceDisconnected" as the reason +* for the destruction of the connection and stream(s). +*

+*

+* While you can use the forceDisconnect() method to terminate your own connection, +* calling the disconnect() method is simpler. +*

+*

+* The OT object dispatches an exception event if the user's role +* does not include permissions required to force other users to disconnect. +* You define a user's role when you create the user token using the +* generate_token() method using +* OpenTok +* server-side libraries or the +* Dashboard page. +* See ExceptionEvent and OT.on(). +*

+*

+* The application throws an error if the session is not connected. +*

+* +*
Events dispatched:
+* +*

+* connectionDestroyed (ConnectionEvent) — +* On clients other than which had the connection terminated. +*

+*

+* exception (ExceptionEvent) — +* The user's role does not allow forcing other user's to disconnect (event.code = +* 1530), +* or the specified stream is not publishing to the session (event.code = 1535). +*

+*

+* sessionDisconnected +* (SessionDisconnectEvent) — +* On the client which has the connection terminated. +*

+*

+* streamDestroyed (StreamEvent) — +* If streams are stopped as a result of the connection ending. +*

+* +* @param {Connection} connection The connection to be disconnected from the session. +* This value can either be a Connection object or a connection +* ID (which can be obtained from the connectionId property of the Connection object). +* +* @param {Function} completionHandler (Optional) A function to be called when the call to the +* forceDiscononnect() method succeeds or fails. This function takes one parameter +* — error. On success, the completionHandler function is +* not passed any arguments. On error, the function is passed an error object +* parameter. The error object, defined by the Error +* class, has two properties: code (an integer) +* and message (a string), which identify the cause of the failure. +* Calling forceDisconnect() fails if the role assigned to your +* token is not "moderator"; in this case error.code is set to 1520. The following +* code adds a completionHandler when calling the forceDisconnect() +* method: +*
+* session.forceDisconnect(connection, function (error) {
+*   if (error) {
+*       console.log(error);
+*     } else {
+*       console.log("Connection forced to disconnect: " + connection.id);
+*     }
+*   });
+* 
+* +* @method #forceDisconnect +* @memberOf Session +*/ + + this.forceDisconnect = function(connectionOrConnectionId, completionHandler) { + if (this.isNot('connected')) { + var notConnectedErrorMsg = 'Cannot call forceDisconnect(). You are not ' + + 'connected to the session.'; + dispatchError(OT.ExceptionCodes.NOT_CONNECTED, notConnectedErrorMsg, completionHandler); + return; + } + + var notPermittedErrorMsg = 'This token does not allow forceDisconnect. ' + + 'The role must be at least `moderator` to enable this functionality'; + + if (permittedTo('forceDisconnect')) { + var connectionId = typeof connectionOrConnectionId === 'string' ? + connectionOrConnectionId : connectionOrConnectionId.id; + + _socket.forceDisconnect(connectionId, function(err) { + if (err) { + dispatchError(OT.ExceptionCodes.UNABLE_TO_FORCE_DISCONNECT, + notPermittedErrorMsg, completionHandler); + + } else if (completionHandler && OT.$.isFunction(completionHandler)) { + completionHandler.apply(null, arguments); + } + }); + } else { + // if this throws an error the handleJsException won't occur + dispatchError(OT.ExceptionCodes.UNABLE_TO_FORCE_DISCONNECT, + notPermittedErrorMsg, completionHandler); + } }; -})(window); -!(function() { +/** +* Forces the publisher of the specified stream to stop publishing the stream. +* +*

+* Calling this method causes the Session object to dispatch a streamDestroyed +* event on all clients that are subscribed to the stream (including the client that is +* publishing the stream). The reason property of the StreamEvent object is +* set to "forceUnpublished". +*

+*

+* The OT object dispatches an exception event if the user's role +* does not include permissions required to force other users to unpublish. +* You define a user's role when you create the user token using the generate_token() +* method using the +* OpenTok +* server-side libraries or the Dashboard +* page. +* You pass the token string as a parameter of the connect() method of the Session +* object. See ExceptionEvent and +* OT.on(). +*

+* +*
Events dispatched:
+* +*

+* exception (ExceptionEvent) — +* The user's role does not allow forcing other users to unpublish. +*

+*

+* streamDestroyed (StreamEvent) — +* The stream has been unpublished. The Session object dispatches this on all clients +* subscribed to the stream, as well as on the publisher's client. +*

+* +* @param {Stream} stream The stream to be unpublished. +* +* @param {Function} completionHandler (Optional) A function to be called when the call to the +* forceUnpublish() method succeeds or fails. This function takes one parameter +* — error. On success, the completionHandler function is +* not passed any arguments. On error, the function is passed an error object +* parameter. The error object, defined by the Error +* class, has two properties: code (an integer) +* and message (a string), which identify the cause of the failure. Calling +* forceUnpublish() fails if the role assigned to your token is not "moderator"; +* in this case error.code is set to 1530. The following code adds a +* completionHandler when calling the forceUnpublish() method: +*
+* session.forceUnpublish(stream, function (error) {
+*   if (error) {
+*       console.log(error);
+*     } else {
+*       console.log("Connection forced to disconnect: " + connection.id);
+*     }
+*   });
+* 
+* +* @method #forceUnpublish +* @memberOf Session +*/ + this.forceUnpublish = function(streamOrStreamId, completionHandler) { + if (this.isNot('connected')) { + var notConnectedErrorMsg = 'Cannot call forceUnpublish(). You are not ' + + 'connected to the session.'; + dispatchError(OT.ExceptionCodes.NOT_CONNECTED, notConnectedErrorMsg, completionHandler); + return; + } - // The default constraints - var defaultConstraints = { - audio: true, - video: true + var notPermittedErrorMsg = 'This token does not allow forceUnpublish. ' + + 'The role must be at least `moderator` to enable this functionality'; + + if (permittedTo('forceUnpublish')) { + var stream = typeof streamOrStreamId === 'string' ? + this.streams.get(streamOrStreamId) : streamOrStreamId; + + _socket.forceUnpublish(stream.id, function(err) { + if (err) { + dispatchError(OT.ExceptionCodes.UNABLE_TO_FORCE_UNPUBLISH, + notPermittedErrorMsg, completionHandler); + } else if (completionHandler && OT.$.isFunction(completionHandler)) { + completionHandler.apply(null, arguments); + } + }); + } else { + // if this throws an error the handleJsException won't occur + dispatchError(OT.ExceptionCodes.UNABLE_TO_FORCE_UNPUBLISH, + notPermittedErrorMsg, completionHandler); + } }; + this.getStateManager = function() { + OT.warn('Fixme: Have not implemented session.getStateManager'); + }; + + this.isConnected = function() { + return this.is('connected'); + }; + + this.capabilities = new OT.Capabilities([]); + +/** + * Dispatched when an archive recording of the session starts. + * + * @name archiveStarted + * @event + * @memberof Session + * @see ArchiveEvent + * @see Archiving overview. + */ + +/** + * Dispatched when an archive recording of the session stops. + * + * @name archiveStopped + * @event + * @memberof Session + * @see ArchiveEvent + * @see Archiving overview. + */ + +/** + * Dispatched when a new client (including your own) has connected to the session, and for + * every client in the session when you first connect. (The Session object also dispatches + * a sessionConnected event when your local client connects.) + * + * @name connectionCreated + * @event + * @memberof Session + * @see ConnectionEvent + * @see OT.initSession() + */ + +/** + * A client, other than your own, has disconnected from the session. + * @name connectionDestroyed + * @event + * @memberof Session + * @see ConnectionEvent + */ + +/** + * The page has connected to an OpenTok session. This event is dispatched asynchronously + * in response to a successful call to the connect() method of a Session + * object. Before calling the connect() method, initialize the session by + * calling the OT.initSession() method. For a code example and more details, + * see Session.connect(). + * @name sessionConnected + * @event + * @memberof Session + * @see SessionConnectEvent + * @see Session.connect() + * @see OT.initSession() + */ + +/** + * The client has disconnected from the session. This event may be dispatched asynchronously + * in response to a successful call to the disconnect() method of the Session object. + * The event may also be disptached if a session connection is lost inadvertantly, as in the case + * of a lost network connection. + *

+ * The default behavior is that all Subscriber objects are unsubscribed and removed from the + * HTML DOM. Each Subscriber object dispatches a destroyed event when the element is + * removed from the HTML DOM. If you call the preventDefault() method in the event + * listener for the sessionDisconnect event, the default behavior is prevented, and + * you can, optionally, clean up Subscriber objects using your own code. +* + * @name sessionDisconnected + * @event + * @memberof Session + * @see Session.disconnect() + * @see Session.forceDisconnect() + * @see SessionDisconnectEvent + */ + +/** + * A new stream, published by another client, has been created on this session. For streams + * published by your own client, the Publisher object dispatches a streamCreated + * event. For a code example and more details, see {@link StreamEvent}. + * @name streamCreated + * @event + * @memberof Session + * @see StreamEvent + * @see Session.publish() + */ + +/** + * A stream from another client has stopped publishing to the session. + *

+ * The default behavior is that all Subscriber objects that are subscribed to the stream are + * unsubscribed and removed from the HTML DOM. Each Subscriber object dispatches a + * destroyed event when the element is removed from the HTML DOM. If you call the + * preventDefault() method in the event listener for the + * streamDestroyed event, the default behavior is prevented and you can clean up + * Subscriber objects using your own code. See + * Session.getSubscribersForStream(). + *

+ * For streams published by your own client, the Publisher object dispatches a + * streamDestroyed event. + *

+ * For a code example and more details, see {@link StreamEvent}. + * @name streamDestroyed + * @event + * @memberof Session + * @see StreamEvent + */ + +/** + * Defines an event dispatched when property of a stream has changed. This can happen in + * in the following conditions: + *

+ *

    + *
  • A stream has started or stopped publishing audio or video (see + * Publisher.publishAudio() and + * Publisher.publishVideo()). Note + * that a subscriber's video can be disabled or enabled for reasons other than + * the publisher disabling or enabling it. A Subscriber object dispatches + * videoDisabled and videoEnabled events in all + * conditions that cause the subscriber's stream to be disabled or enabled. + *
  • + *
  • The videoDimensions property of the Stream object has + * changed (see Stream.videoDimensions). + *
  • + *
  • The videoType property of the Stream object has changed. + * This can happen in a stream published by a mobile device. (See + * Stream.videoType.) + *
  • + *
+ * + * @name streamPropertyChanged + * @event + * @memberof Session + * @see StreamPropertyChangedEvent + * @see Publisher.publishAudio() + * @see Publisher.publishVideo() + * @see Stream.hasAudio + * @see Stream.hasVideo + * @see Stream.videoDimensions + * @see Subscriber videoDisabled event + * @see Subscriber videoEnabled event + */ + +/** + * A signal was received from the session. The SignalEvent + * class defines this event object. It includes the following properties: + *
    + *
  • data — (String) The data string sent with the signal (if there + * is one).
  • + *
  • from — (Connection) The Connection + * corresponding to the client that sent with the signal.
  • + *
  • type — (String) The type assigned to the signal (if there is + * one).
  • + *
+ *

+ * You can register to receive all signals sent in the session, by adding an event handler + * for the signal event. For example, the following code adds an event handler + * to process all signals sent in the session: + *

+ * session.on("signal", function(event) {
+ *   console.log("Signal sent from connection: " + event.from.id);
+ *   console.log("Signal data: " + event.data);
+ * });
+ * 
+ *

You can register for signals of a specfied type by adding an event handler for the + * signal:type event (replacing type with the actual type string + * to filter on). + * + * @name signal + * @event + * @memberof Session + * @see Session.signal() + * @see SignalEvent + * @see signal:type event + */ + +/** + * A signal of the specified type was received from the session. The + * SignalEvent class defines this event object. + * It includes the following properties: + *

    + *
  • data — (String) The data string sent with the signal.
  • + *
  • from — (Connection) The Connection + * corresponding to the client that sent with the signal.
  • + *
  • type — (String) The type assigned to the signal (if there is one). + *
  • + *
+ *

+ * You can register for signals of a specfied type by adding an event handler for the + * signal:type event (replacing type with the actual type string + * to filter on). For example, the following code adds an event handler for signals of + * type "foo": + *

+ * session.on("signal:foo", function(event) {
+ *   console.log("foo signal sent from connection " + event.from.id);
+ *   console.log("Signal data: " + event.data);
+ * });
+ * 
+ *

+ * You can register to receive all signals sent in the session, by adding an event + * handler for the signal event. + * + * @name signal:type + * @event + * @memberof Session + * @see Session.signal() + * @see SignalEvent + * @see signal event + */ +}; + +// tb_require('../helpers/helpers.js') +// tb_require('../helpers/lib/get_user_media.js') +// tb_require('../helpers/lib/widget_view.js') +// tb_require('./analytics.js') +// tb_require('./events.js') +// tb_require('./system_requirements.js') +// tb_require('./stylable_component.js') +// tb_require('./stream.js') +// tb_require('./connection.js') +// tb_require('./publishing_state.js') +// tb_require('./environment_loader.js') +// tb_require('./audio_context.js') +// tb_require('./chrome/chrome.js') +// tb_require('./chrome/backing_bar.js') +// tb_require('./chrome/name_panel.js') +// tb_require('./chrome/mute_button.js') +// tb_require('./chrome/archiving.js') +// tb_require('./chrome/audio_level_meter.js') +// tb_require('./peer_connection/publisher_peer_connection.js') +// tb_require('./screensharing/register.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ + +// The default constraints +var defaultConstraints = { + audio: true, + video: true +}; + /** * The Publisher object provides the mechanism through which control of the * published stream is accomplished. Calling the OT.initPublisher() method @@ -17582,4691 +22046,2068 @@ var SDPHelpers = { * @class Publisher * @augments EventDispatcher */ - OT.Publisher = function() { - // Check that the client meets the minimum requirements, if they don't the upgrade - // flow will be triggered. - if (!OT.checkSystemRequirements()) { - OT.upgradeSystemRequirements(); - return; +OT.Publisher = function(options) { + // Check that the client meets the minimum requirements, if they don't the upgrade + // flow will be triggered. + if (!OT.checkSystemRequirements()) { + OT.upgradeSystemRequirements(); + return; + } + + var _guid = OT.Publisher.nextId(), + _domId, + _widgetView, + _targetElement, + _stream, + _streamId, + _webRTCStream, + _session, + _peerConnections = {}, + _loaded = false, + _publishStartTime, + _microphone, + _chrome, + _audioLevelMeter, + _properties, + _validResolutions, + _validFrameRates = [ 1, 7, 15, 30 ], + _prevStats, + _state, + _iceServers, + _audioLevelCapable = OT.$.hasCapabilities('webAudio'), + _audioLevelSampler, + _isScreenSharing = options && ( + options.videoSource === 'screen' || + options.videoSource === 'window' || + options.videoSource === 'tab' || + options.videoSource === 'application' + ), + _connectivityAttemptPinger, + _publisher = this; + + _properties = OT.$.defaults(options || {}, { + publishAudio: _isScreenSharing ? false : true, + publishVideo: true, + mirror: _isScreenSharing ? false : true, + showControls: true, + fitMode: _isScreenSharing ? 'contain' : 'cover', + audioFallbackEnabled: _isScreenSharing ? false : true, + maxResolution: _isScreenSharing ? { width: 1920, height: 1920 } : undefined + }); + + _validResolutions = { + '320x240': {width: 320, height: 240}, + '640x480': {width: 640, height: 480}, + '1280x720': {width: 1280, height: 720} + }; + + if (_isScreenSharing) { + if (window.location.protocol !== 'https:') { + OT.warn('Screen Sharing typically requires pages to be loadever over HTTPS - unless this ' + + 'browser is configured locally to allow non-SSL pages, permission will be denied ' + + 'without user input.'); } + } - var _guid = OT.Publisher.nextId(), - _domId, - _container, - _targetElement, - _stream, - _streamId, - _webRTCStream, - _session, - _peerConnections = {}, - _loaded = false, - _publishProperties, - _publishStartTime, - _microphone, - _chrome, - _audioLevelMeter, - _analytics = new OT.Analytics(), - _validResolutions, - _validFrameRates = [ 1, 7, 15, 30 ], - _prevStats, - _state, - _iceServers, - _audioLevelCapable = OT.$.hasCapabilities('webAudio'), - _audioLevelSampler, - _publisher = this; - - _validResolutions = { - '320x240': {width: 320, height: 240}, - '640x480': {width: 640, height: 480}, - '1280x720': {width: 1280, height: 720} - }; - - _prevStats = { - 'timeStamp' : OT.$.now() - }; - - OT.$.eventing(this); - - if(_audioLevelCapable) { - _audioLevelSampler = new OT.AnalyserAudioLevelSampler(OT.audioContext()); - - var audioLevelRunner = new OT.IntervalRunner(function() { - _audioLevelSampler.sample(function(audioInputLevel) { - OT.$.requestAnimationFrame(function() { - _publisher.dispatchEvent( - new OT.AudioLevelUpdatedEvent(audioInputLevel)); - }); - }); - }, 60); - - this.on({ - 'audioLevelUpdated:added': function(count) { - if (count === 1) { - audioLevelRunner.start(); - } - }, - 'audioLevelUpdated:removed': function(count) { - if (count === 0) { - audioLevelRunner.stop(); - } - } - }); - } - - OT.StylableComponent(this, { - showArchiveStatus: true, - nameDisplayMode: 'auto', - buttonDisplayMode: 'auto', - audioLevelDisplayMode: 'auto', - backgroundImageURI: null - }); - - /// Private Methods - var logAnalyticsEvent = function(action, variation, payloadType, payload) { - _analytics.logEvent({ - action: action, - variation: variation, - 'payload_type': payloadType, - payload: payload, - 'session_id': _session ? _session.sessionId : null, - 'connection_id': _session && - _session.isConnected() ? _session.connection.connectionId : null, - 'partner_id': _session ? _session.apiKey : OT.APIKEY, - streamId: _stream ? _stream.id : null, - 'widget_id': _guid, - 'widget_type': 'Publisher' - }); - }, - - recordQOS = OT.$.bind(function(connection, parsedStats) { - if(!_state.isPublishing()) { - return; - } - var QoSBlob = { - 'widget_type': 'Publisher', - 'stream_type': 'WebRTC', - sessionId: _session ? _session.sessionId : null, - connectionId: _session && _session.isConnected() ? - _session.connection.connectionId : null, - partnerId: _session ? _session.apiKey : OT.APIKEY, - streamId: _stream ? _stream.id : null, - width: _container ? OT.$.width(_container.domElement) : undefined, - height: _container ? OT.$.height(_container.domElement) : undefined, - widgetId: _guid, - version: OT.properties.version, - 'media_server_name': _session ? _session.sessionInfo.messagingServer : null, - p2pFlag: _session ? _session.sessionInfo.p2pEnabled : false, - duration: _publishStartTime ? new Date().getTime() - _publishStartTime.getTime() : 0, - 'remote_connection_id': connection.id - }; - - _analytics.logQOS( OT.$.extend(QoSBlob, parsedStats) ); - this.trigger('qos', parsedStats); - }, this), - - /// Private Events - - stateChangeFailed = function(changeFailed) { - OT.error('Publisher State Change Failed: ', changeFailed.message); - OT.debug(changeFailed); - }, - - onLoaded = function() { - if (_state.isDestroyed()) { - // The publisher was destroyed before loading finished - return; - } - - OT.debug('OT.Publisher.onLoaded'); - - _state.set('MediaBound'); - - // If we have a session and we haven't created the stream yet then - // wait until that is complete before hiding the loading spinner - _container.loading(this.session ? !_stream : false); - - _loaded = true; - - _createChrome.call(this); - - this.trigger('initSuccess'); - this.trigger('loaded', this); - }, - - onLoadFailure = function(reason) { - logAnalyticsEvent('publish', 'Failure', 'reason', - 'Publisher PeerConnection Error: ' + reason); - - _state.set('Failed'); - this.trigger('publishComplete', new OT.Error(OT.ExceptionCodes.P2P_CONNECTION_FAILED, - 'Publisher PeerConnection Error: ' + reason)); - - OT.handleJsException('Publisher PeerConnection Error: ' + reason, - OT.ExceptionCodes.P2P_CONNECTION_FAILED, { - session: _session, - target: this - }); - }, - - onStreamAvailable = function(webOTStream) { - OT.debug('OT.Publisher.onStreamAvailable'); - - _state.set('BindingMedia'); - - cleanupLocalStream(); - _webRTCStream = webOTStream; - - _microphone = new OT.Microphone(_webRTCStream, !_publishProperties.publishAudio); - this.publishVideo(_publishProperties.publishVideo && - _webRTCStream.getVideoTracks().length > 0); - - this.accessAllowed = true; - this.dispatchEvent(new OT.Event(OT.Event.names.ACCESS_ALLOWED, false)); - - var videoContainerOptions = { - muted: true, - error: OT.$.bind(onVideoError, this) - }; - - _targetElement = _container.bindVideo(_webRTCStream, - videoContainerOptions, - OT.$.bind(function(err) { - if (err) { - onLoadFailure.call(this, err); - return; - } - - onLoaded.call(this); - }, this)); - - if(_audioLevelSampler && webOTStream.getAudioTracks().length > 0) { - _audioLevelSampler.webOTStream = webOTStream; - } - - }, - - onStreamAvailableError = function(error) { - OT.error('OT.Publisher.onStreamAvailableError ' + error.name + ': ' + error.message); - - _state.set('Failed'); - this.trigger('publishComplete', new OT.Error(OT.ExceptionCodes.UNABLE_TO_PUBLISH, - error.message)); - - if (_container) _container.destroy(); - - logAnalyticsEvent('publish', 'Failure', 'reason', - 'GetUserMedia:Publisher failed to access camera/mic: ' + error.message); - - OT.handleJsException('Publisher failed to access camera/mic: ' + error.message, - OT.ExceptionCodes.UNABLE_TO_PUBLISH, { - session: _session, - target: this - }); - }, - - // The user has clicked the 'deny' button the the allow access dialog - // (or it's set to always deny) - onAccessDenied = function(error) { - OT.error('OT.Publisher.onStreamAvailableError Permission Denied'); - - _state.set('Failed'); - this.trigger('publishComplete', new OT.Error(OT.ExceptionCodes.UNABLE_TO_PUBLISH, - 'Publisher Access Denied: Permission Denied' + - (error.message ? ': ' + error.message : ''))); - - logAnalyticsEvent('publish', 'Failure', 'reason', - 'GetUserMedia:Publisher Access Denied: Permission Denied'); - - this.dispatchEvent(new OT.Event(OT.Event.names.ACCESS_DENIED)); - }, - - accessDialogWasOpened = false, - - onAccessDialogOpened = function() { - - accessDialogWasOpened = true; - - logAnalyticsEvent('accessDialog', 'Opened', '', ''); - - this.dispatchEvent(new OT.Event(OT.Event.names.ACCESS_DIALOG_OPENED, true)); - }, - - onAccessDialogClosed = function() { - logAnalyticsEvent('accessDialog', 'Closed', '', ''); - - this.dispatchEvent( new OT.Event(OT.Event.names.ACCESS_DIALOG_CLOSED, false)); - }, - - onVideoError = function(errorCode, errorReason) { - OT.error('OT.Publisher.onVideoError'); - - var message = errorReason + (errorCode ? ' (' + errorCode + ')' : ''); - logAnalyticsEvent('stream', null, 'reason', - 'Publisher while playing stream: ' + message); - - _state.set('Failed'); - - if (_state.isAttemptingToPublish()) { - this.trigger('publishComplete', new OT.Error(OT.ExceptionCodes.UNABLE_TO_PUBLISH, - message)); - } else { - this.trigger('error', message); - } - - OT.handleJsException('Publisher error playing stream: ' + message, - OT.ExceptionCodes.UNABLE_TO_PUBLISH, { - session: _session, - target: this - }); - }, - - onPeerDisconnected = function(peerConnection) { - OT.debug('OT.Subscriber has been disconnected from the Publisher\'s PeerConnection'); - - this.cleanupSubscriber(peerConnection.remoteConnection().id); - }, - - onPeerConnectionFailure = function(code, reason, peerConnection, prefix) { - logAnalyticsEvent('publish', 'Failure', 'reason|hasRelayCandidates', - (prefix ? prefix : '') + [':Publisher PeerConnection with connection ' + - (peerConnection && peerConnection.remoteConnection && - peerConnection.remoteConnection().id) + ' failed: ' + - reason, peerConnection.hasRelayCandidates() - ].join('|')); - - OT.handleJsException('Publisher PeerConnection Error: ' + reason, - OT.ExceptionCodes.UNABLE_TO_PUBLISH, { - session: _session, - target: this - }); - - // We don't call cleanupSubscriber as it also logs a - // disconnected analytics event, which we don't want in this - // instance. The duplication is crufty though and should - // be tidied up. - - delete _peerConnections[peerConnection.remoteConnection().id]; - }, - - /// Private Helpers - - // Assigns +stream+ to this publisher. The publisher listens - // for a bunch of events on the stream so it can respond to - // changes. - assignStream = OT.$.bind(function(stream) { - this.stream = _stream = stream; - _stream.on('destroyed', this.disconnect, this); - - _state.set('Publishing'); - _container.loading(!_loaded); - _publishStartTime = new Date(); - - this.trigger('publishComplete', null, this); - - this.dispatchEvent(new OT.StreamEvent('streamCreated', stream, null, false)); - - logAnalyticsEvent('publish', 'Success', 'streamType:streamId', 'WebRTC:' + _streamId); - }, this), - - // Clean up our LocalMediaStream - cleanupLocalStream = function() { - if (_webRTCStream) { - // Stop revokes our access cam and mic access for this instance - // of localMediaStream. - _webRTCStream.stop(); - _webRTCStream = null; - } - }, - - createPeerConnectionForRemote = function(remoteConnection) { - var peerConnection = _peerConnections[remoteConnection.id]; - - if (!peerConnection) { - var startConnectingTime = OT.$.now(); - - logAnalyticsEvent('createPeerConnection', 'Attempt', '', ''); - - // Cleanup our subscriber when they disconnect - remoteConnection.on('destroyed', - OT.$.bind(this.cleanupSubscriber, this, remoteConnection.id)); - - peerConnection = _peerConnections[remoteConnection.id] = new OT.PublisherPeerConnection( - remoteConnection, - _session, - _streamId, - _webRTCStream - ); - - peerConnection.on({ - connected: function() { - logAnalyticsEvent('createPeerConnection', 'Success', 'pcc|hasRelayCandidates', [ - parseInt(OT.$.now() - startConnectingTime, 10), - peerConnection.hasRelayCandidates() - ].join('|')); - }, - disconnected: onPeerDisconnected, - error: onPeerConnectionFailure, - qos: recordQOS - }, this); - - peerConnection.init(_iceServers); - } - - return peerConnection; - }, - - /// Chrome - - // If mode is false, then that is the mode. If mode is true then we'll - // definitely display the button, but we'll defer the model to the - // Publishers buttonDisplayMode style property. - chromeButtonMode = function(mode) { - if (mode === false) return 'off'; - - var defaultMode = this.getStyle('buttonDisplayMode'); - - // The default model is false, but it's overridden by +mode+ being true - if (defaultMode === false) return 'on'; - - // defaultMode is either true or auto. - return defaultMode; - }, - - updateChromeForStyleChange = function(key, value) { - if (!_chrome) return; - - switch(key) { - case 'nameDisplayMode': - _chrome.name.setDisplayMode(value); - _chrome.backingBar.setNameMode(value); - break; - - case 'showArchiveStatus': - logAnalyticsEvent('showArchiveStatus', 'styleChange', 'mode', value ? 'on': 'off'); - _chrome.archive.setShowArchiveStatus(value); - break; - - case 'buttonDisplayMode': - _chrome.muteButton.setDisplayMode(value); - _chrome.backingBar.setMuteMode(value); - break; - - case 'audioLevelDisplayMode': - _chrome.audioLevel.setDisplayMode(value); - break; - - case 'backgroundImageURI': - _container.setBackgroundImageURI(value); - } - }, - - _createChrome = function() { - - if(!this.getStyle('showArchiveStatus')) { - logAnalyticsEvent('showArchiveStatus', 'createChrome', 'mode', 'off'); - } - - var widgets = { - backingBar: new OT.Chrome.BackingBar({ - nameMode: !_publishProperties.name ? 'off' : this.getStyle('nameDisplayMode'), - muteMode: chromeButtonMode.call(this, this.getStyle('buttonDisplayMode')) - }), - - name: new OT.Chrome.NamePanel({ - name: _publishProperties.name, - mode: this.getStyle('nameDisplayMode') - }), - - muteButton: new OT.Chrome.MuteButton({ - muted: _publishProperties.publishAudio === false, - mode: chromeButtonMode.call(this, this.getStyle('buttonDisplayMode')) - }), - - archive: new OT.Chrome.Archiving({ - show: this.getStyle('showArchiveStatus'), - archiving: false - }) - }; - - if (_audioLevelCapable) { - var audioLevelTransformer = new OT.AudioLevelTransformer(); - - var audioLevelUpdatedHandler = function(evt) { - _audioLevelMeter.setValue(audioLevelTransformer.transform(evt.audioLevel)); - }; - - _audioLevelMeter = new OT.Chrome.AudioLevelMeter({ - mode: this.getStyle('audioLevelDisplayMode'), - onActivate: function() { - _publisher.on('audioLevelUpdated', audioLevelUpdatedHandler); - }, - onPassivate: function() { - _publisher.off('audioLevelUpdated', audioLevelUpdatedHandler); - } - }); - - widgets.audioLevel = _audioLevelMeter; - } - - _chrome = new OT.Chrome({ - parent: _container.domElement - }).set(widgets).on({ - muted: OT.$.bind(this.publishAudio, this, false), - unmuted: OT.$.bind(this.publishAudio, this, true) - }); - - if(_audioLevelMeter && this.getStyle('audioLevelDisplayMode') === 'auto') { - _audioLevelMeter[_container.audioOnly() ? 'show' : 'hide'](); - } - }, - - reset = OT.$.bind(function() { - if (_chrome) { - _chrome.destroy(); - _chrome = null; - } - - this.disconnect(); - - _microphone = null; - - if (_targetElement) { - _targetElement.destroy(); - _targetElement = null; - } - - cleanupLocalStream(); - - if (_container) { - _container.destroy(); - _container = null; - } - - if (_session) { - this._.unpublishFromSession(_session, 'reset'); - } - - this.id = _domId = null; - this.stream = _stream = null; - _loaded = false; - - this.session = _session = null; - - if (!_state.isDestroyed()) _state.set('NotPublishing'); - }, this); - - var setAudioOnly = function(audioOnly) { - if (_container) { - _container.audioOnly(audioOnly); - _container.showPoster(audioOnly); - } - - if (_audioLevelMeter && _publisher.getStyle('audioLevelDisplayMode') === 'auto') { - _audioLevelMeter[audioOnly ? 'show' : 'hide'](); - } - }; - - this.publish = function(targetElement, properties) { - OT.debug('OT.Publisher: publish'); - - if ( _state.isAttemptingToPublish() || _state.isPublishing() ) reset(); - _state.set('GetUserMedia'); - - _publishProperties = OT.$.defaults(properties || {}, { - publishAudio : true, - publishVideo : true, - mirror: true - }); - - if (!_publishProperties.constraints) { - _publishProperties.constraints = OT.$.clone(defaultConstraints); - - if(_publishProperties.audioSource === null || _publishProperties.audioSource === false) { - _publishProperties.constraints.audio = false; - _publishProperties.publishAudio = false; - } else { - if(typeof _publishProperties.audioSource === 'object') { - if(_publishProperties.audioSource.deviceId != null) { - _publishProperties.audioSource = _publishProperties.audioSource.deviceId; - } else { - OT.warn('Invalid audioSource passed to Publisher. Expected either a device ID'); - } - } - - if (_publishProperties.audioSource) { - if (typeof _publishProperties.constraints.audio !== 'object') { - _publishProperties.constraints.audio = {}; - } - if (!_publishProperties.constraints.audio.mandatory) { - _publishProperties.constraints.audio.mandatory = {}; - } - if (!_publishProperties.constraints.audio.optional) { - _publishProperties.constraints.audio.optional = []; - } - _publishProperties.constraints.audio.mandatory.sourceId = - _publishProperties.audioSource; - } - } - - if(_publishProperties.videoSource === null || _publishProperties.videoSource === false) { - _publishProperties.constraints.video = false; - _publishProperties.publishVideo = false; - } else { - - if(typeof _publishProperties.videoSource === 'object') { - if(_publishProperties.videoSource.deviceId != null) { - _publishProperties.videoSource = _publishProperties.videoSource.deviceId; - } else { - OT.warn('Invalid videoSource passed to Publisher. Expected either a device ID'); - } - } - - if (_publishProperties.videoSource) { - if (typeof _publishProperties.constraints.video !== 'object') { - _publishProperties.constraints.video = {}; - } - if (!_publishProperties.constraints.video.mandatory) { - _publishProperties.constraints.video.mandatory = {}; - } - if (!_publishProperties.constraints.video.optional) { - _publishProperties.constraints.video.optional = []; - } - _publishProperties.constraints.video.mandatory.sourceId = - _publishProperties.videoSource; - } - - if (_publishProperties.resolution) { - if (_publishProperties.resolution !== void 0 && - !_validResolutions.hasOwnProperty(_publishProperties.resolution)) { - OT.warn('Invalid resolution passed to the Publisher. Got: ' + - _publishProperties.resolution + ' expecting one of "' + - OT.$.keys(_validResolutions).join('","') + '"'); - } else { - _publishProperties.videoDimensions = _validResolutions[_publishProperties.resolution]; - if (typeof _publishProperties.constraints.video !== 'object') { - _publishProperties.constraints.video = {}; - } - if (!_publishProperties.constraints.video.mandatory) { - _publishProperties.constraints.video.mandatory = {}; - } - if (!_publishProperties.constraints.video.optional) { - _publishProperties.constraints.video.optional = []; - } - _publishProperties.constraints.video.optional = - _publishProperties.constraints.video.optional.concat([ - {minWidth: _publishProperties.videoDimensions.width}, - {maxWidth: _publishProperties.videoDimensions.width}, - {minHeight: _publishProperties.videoDimensions.height}, - {maxHeight: _publishProperties.videoDimensions.height} - ]); - } - } - - if (_publishProperties.frameRate !== void 0 && - OT.$.arrayIndexOf(_validFrameRates, _publishProperties.frameRate) === -1) { - OT.warn('Invalid frameRate passed to the publisher got: ' + - _publishProperties.frameRate + ' expecting one of ' + _validFrameRates.join(',')); - delete _publishProperties.frameRate; - } else if (_publishProperties.frameRate) { - if (typeof _publishProperties.constraints.video !== 'object') { - _publishProperties.constraints.video = {}; - } - if (!_publishProperties.constraints.video.mandatory) { - _publishProperties.constraints.video.mandatory = {}; - } - if (!_publishProperties.constraints.video.optional) { - _publishProperties.constraints.video.optional = []; - } - _publishProperties.constraints.video.optional = - _publishProperties.constraints.video.optional.concat([ - { minFrameRate: _publishProperties.frameRate }, - { maxFrameRate: _publishProperties.frameRate } - ]); - } - } - - } else { - OT.warn('You have passed your own constraints not using ours'); - } - - - if (_publishProperties.style) { - this.setStyle(_publishProperties.style, null, true); - } - - if (_publishProperties.name) { - _publishProperties.name = _publishProperties.name.toString(); - } - - _publishProperties.classNames = 'OT_root OT_publisher'; - - // Defer actually creating the publisher DOM nodes until we know - // the DOM is actually loaded. - OT.onLoad(function() { - _container = new OT.WidgetView(targetElement, _publishProperties); - this.id = _domId = _container.domId(); - this.element = _container.domElement; - - OT.$.shouldAskForDevices(OT.$.bind(function(devices) { - if(!devices.video) { - OT.warn('Setting video constraint to false, there are no video sources'); - _publishProperties.constraints.video = false; - } - if(!devices.audio) { - OT.warn('Setting audio constraint to false, there are no audio sources'); - _publishProperties.constraints.audio = false; - } - OT.$.getUserMedia( - _publishProperties.constraints, - OT.$.bind(onStreamAvailable, this), - OT.$.bind(onStreamAvailableError, this), - OT.$.bind(onAccessDialogOpened, this), - OT.$.bind(onAccessDialogClosed, this), - OT.$.bind(onAccessDenied, this) - ); - }, this)); - - }, this); - - return this; - }; - - /** - * Starts publishing audio (if it is currently not being published) - * when the value is true; stops publishing audio - * (if it is currently being published) when the value is false. - * - * @param {Boolean} value Whether to start publishing audio (true) - * or not (false). - * - * @see OT.initPublisher() - * @see Stream.hasAudio - * @see StreamPropertyChangedEvent - * @method #publishAudio - * @memberOf Publisher - */ - this.publishAudio = function(value) { - _publishProperties.publishAudio = value; - - if (_microphone) { - _microphone.muted(!value); - } - - if (_chrome) { - _chrome.muteButton.muted(!value); - } - - if (_session && _stream) { - _stream.setChannelActiveState('audio', value); - } - - return this; - }; - - /** - * Starts publishing video (if it is currently not being published) - * when the value is true; stops publishing video - * (if it is currently being published) when the value is false. - * - * @param {Boolean} value Whether to start publishing video (true) - * or not (false). - * - * @see OT.initPublisher() - * @see Stream.hasVideo - * @see StreamPropertyChangedEvent - * @method #publishVideo - * @memberOf Publisher - */ - this.publishVideo = function(value) { - var oldValue = _publishProperties.publishVideo; - _publishProperties.publishVideo = value; - - if (_session && _stream && _publishProperties.publishVideo !== oldValue) { - _stream.setChannelActiveState('video', value); - } - - // We currently do this event if the value of publishVideo has not changed - // This is because the state of the video tracks enabled flag may not match - // the value of publishVideo at this point. This will be tidied up shortly. - if (_webRTCStream) { - var videoTracks = _webRTCStream.getVideoTracks(); - for (var i=0, num=videoTracks.length; i - * The Publisher object dispatches a destroyed event when the DOM - * element is removed. - *

- * @method #destroy - * @memberOf Publisher - * @return {Publisher} The Publisher. - */ - this.destroy = function(/* unused */ reason, quiet) { - if (_state.isDestroyed()) return; - _state.set('Destroyed'); - - reset(); - - if (quiet !== true) { - this.dispatchEvent( - new OT.DestroyedEvent( - OT.Event.names.PUBLISHER_DESTROYED, - this, - reason - ), - OT.$.bind(this.off,this) - ); - } - - return this; - }; - - /** - * @methodOf Publisher - * @private - */ - this.disconnect = function() { - // Close the connection to each of our subscribers - for (var fromConnectionId in _peerConnections) { - this.cleanupSubscriber(fromConnectionId); - } - }; - - this.cleanupSubscriber = function(fromConnectionId) { - var pc = _peerConnections[fromConnectionId]; - - if (pc) { - pc.destroy(); - delete _peerConnections[fromConnectionId]; - - logAnalyticsEvent('disconnect', 'PeerConnection', - 'subscriberConnection', fromConnectionId); - } - }; - - - this.processMessage = function(type, fromConnection, message) { - OT.debug('OT.Publisher.processMessage: Received ' + type + ' from ' + fromConnection.id); - OT.debug(message); - - switch (type) { - case 'unsubscribe': - this.cleanupSubscriber(message.content.connection.id); - break; - - default: - var peerConnection = createPeerConnectionForRemote.call(this, fromConnection); - peerConnection.processMessage(type, message); - } - }; - - /** - * Returns the base-64-encoded string of PNG data representing the Publisher video. - * - *

You can use the string as the value for a data URL scheme passed to the src parameter of - * an image file, as in the following:

- * - *
-    *  var imgData = publisher.getImgData();
-    *
-    *  var img = document.createElement("img");
-    *  img.setAttribute("src", "data:image/png;base64," + imgData);
-    *  var imgWin = window.open("about:blank", "Screenshot");
-    *  imgWin.document.write("<body></body>");
-    *  imgWin.document.body.appendChild(img);
-    * 
- * - * @method #getImgData - * @memberOf Publisher - * @return {String} The base-64 encoded string. Returns an empty string if there is no video. - */ - - this.getImgData = function() { - if (!_loaded) { - OT.error('OT.Publisher.getImgData: Cannot getImgData before the Publisher is publishing.'); - - return null; - } - - return _targetElement.imgData(); - }; - - - // API Compatibility layer for Flash Publisher, this could do with some tidyup. - this._ = { - publishToSession: OT.$.bind(function(session) { - // Add session property to Publisher - this.session = _session = session; - - var createStream = function() { - - var streamWidth, - streamHeight; - - // Bail if this.session is gone, it means we were unpublished - // before createStream could finish. - if (!_session) return; - - _state.set('PublishingToSession'); - - var onStreamRegistered = OT.$.bind(function(err, streamId, message) { - if (err) { - // @todo we should respect err.code here and translate it to the local - // client equivalent. - logAnalyticsEvent('publish', 'Failure', 'reason', - 'Publish:' + OT.ExceptionCodes.UNABLE_TO_PUBLISH + ':' + err.message); - if (_state.isAttemptingToPublish()) { - this.trigger('publishComplete', new OT.Error(OT.ExceptionCodes.UNABLE_TO_PUBLISH, - err.message)); - } - return; - } - - this.streamId = _streamId = streamId; - _iceServers = OT.Raptor.parseIceServers(message); - }, this); - - // We set the streamWidth and streamHeight to be the minimum of the requested - // resolution and the actual resolution. - if (_publishProperties.videoDimensions) { - streamWidth = Math.min(_publishProperties.videoDimensions.width, - _targetElement.videoWidth() || 640); - streamHeight = Math.min(_publishProperties.videoDimensions.height, - _targetElement.videoHeight() || 480); - } else { - streamWidth = _targetElement.videoWidth() || 640; - streamHeight = _targetElement.videoHeight() || 480; - } - - session._.streamCreate( - _publishProperties && _publishProperties.name ? _publishProperties.name : '', - OT.VideoOrientation.ROTATED_NORMAL, - streamWidth, - streamHeight, - _publishProperties.publishAudio, - _publishProperties.publishVideo, - _publishProperties.frameRate, - onStreamRegistered - ); - }; - - if (_loaded) createStream.call(this); - else this.on('initSuccess', createStream, this); - - logAnalyticsEvent('publish', 'Attempt', 'streamType', 'WebRTC'); - - return this; - }, this), - - unpublishFromSession: OT.$.bind(function(session, reason) { - if (!_session || session.id !== _session.id) { - OT.warn('The publisher ' + _guid + ' is trying to unpublish from a session ' + - session.id + ' it is not attached to (it is attached to ' + - (_session && _session.id || 'no session') + ')'); - return this; - } - - if (session.isConnected() && this.stream) { - session._.streamDestroy(this.stream.id); - } - - // Disconnect immediately, rather than wait for the WebSocket to - // reply to our destroyStream message. - this.disconnect(); - this.session = _session = null; - - // We're back to being a stand-alone publisher again. - if (!_state.isDestroyed()) _state.set('MediaBound'); - - logAnalyticsEvent('unpublish', 'Success', 'sessionId', session.id); - - this._.streamDestroyed(reason); - - return this; - }, this), - - streamDestroyed: OT.$.bind(function(reason) { - if(OT.$.arrayIndexOf(['reset'], reason) < 0) { - var event = new OT.StreamEvent('streamDestroyed', _stream, reason, true); - var defaultAction = OT.$.bind(function() { - if(!event.isDefaultPrevented()) { - this.destroy(); - } - }, this); - this.dispatchEvent(event, defaultAction); - } - }, this), - - - archivingStatus: OT.$.bind(function(status) { - if(_chrome) { - _chrome.archive.setArchiving(status); - } - - return status; - }, this), - - webRtcStream: function() { - return _webRTCStream; - } - }; - - this.detectDevices = function() { - OT.warn('Fixme: Haven\'t implemented detectDevices'); - }; - - this.detectMicActivity = function() { - OT.warn('Fixme: Haven\'t implemented detectMicActivity'); - }; - - this.getEchoCancellationMode = function() { - OT.warn('Fixme: Haven\'t implemented getEchoCancellationMode'); - return 'fullDuplex'; - }; - - this.setMicrophoneGain = function() { - OT.warn('Fixme: Haven\'t implemented setMicrophoneGain'); - }; - - this.getMicrophoneGain = function() { - OT.warn('Fixme: Haven\'t implemented getMicrophoneGain'); - return 0.5; - }; - - this.setCamera = function() { - OT.warn('Fixme: Haven\'t implemented setCamera'); - }; - - this.setMicrophone = function() { - OT.warn('Fixme: Haven\'t implemented setMicrophone'); - }; - - - // Platform methods: - - this.guid = function() { - return _guid; - }; - - this.videoElement = function() { - return _targetElement.domElement(); - }; - - this.setStream = assignStream; - - this.isWebRTC = true; - - this.isLoading = function() { - return _container && _container.loading(); - }; - - this.videoWidth = function() { - return _targetElement.videoWidth(); - }; - - this.videoHeight = function() { - return _targetElement.videoHeight(); - }; - - // Make read-only: element, guid, _.webRtcStream - - this.on('styleValueChanged', updateChromeForStyleChange, this); - _state = new OT.PublishingState(stateChangeFailed); - - this.accessAllowed = false; - - /** - * Dispatched when the user has clicked the Allow button, granting the - * app access to the camera and microphone. The Publisher object has an - * accessAllowed property which indicates whether the user - * has granted access to the camera and microphone. - * @see Event - * @name accessAllowed - * @event - * @memberof Publisher - */ - - /** - * Dispatched when the user has clicked the Deny button, preventing the - * app from having access to the camera and microphone. - *

- * The default behavior of this event is to display a user interface element - * in the Publisher object, indicating that that user has denied access to - * the camera and microphone. Call the preventDefault() method - * method of the event object in the event listener to prevent this message - * from being displayed. - * @see Event - * @name accessDenied - * @event - * @memberof Publisher - */ - - /** - * Dispatched when the Allow/Deny dialog box is opened. (This is the dialog box in which - * the user can grant the app access to the camera and microphone.) - *

- * The default behavior of this event is to display a message in the browser that instructs - * the user how to enable the camera and microphone. Call the preventDefault() - * method of the event object in the event listener to prevent this message from being displayed. - * @see Event - * @name accessDialogOpened - * @event - * @memberof Publisher - */ - - /** - * Dispatched when the Allow/Deny box is closed. (This is the dialog box in which the - * user can grant the app access to the camera and microphone.) - * @see Event - * @name accessDialogClosed - * @event - * @memberof Publisher - */ - - /** - * Dispatched periodically to indicate the publisher's audio level. The event is dispatched - * up to 60 times per second, depending on the browser. The audioLevel property - * of the event is audio level, from 0 to 1.0. See {@link AudioLevelUpdatedEvent} for more - * information. - *

- * The following example adjusts the value of a meter element that shows volume of the - * publisher. Note that the audio level is adjusted logarithmically and a moving average - * is applied: - *

- *

-    * var movingAvg = null;
-    * publisher.on('audioLevelUpdated', function(event) {
-    *   if (movingAvg === null || movingAvg <= event.audioLevel) {
-    *     movingAvg = event.audioLevel;
-    *   } else {
-    *     movingAvg = 0.7 * movingAvg + 0.3 * event.audioLevel;
-    *   }
-    *
-    *   // 1.5 scaling to map the -30 - 0 dBm range to [0,1]
-    *   var logLevel = (Math.log(movingAvg) / Math.LN10) / 1.5 + 1;
-    *   logLevel = Math.min(Math.max(logLevel, 0), 1);
-    *   document.getElementById('publisherMeter').value = logLevel;
-    * });
-    * 
- *

This example shows the algorithm used by the default audio level indicator displayed - * in an audio-only Publisher. - * - * @name audioLevelUpdated - * @event - * @memberof Publisher - * @see AudioLevelUpdatedEvent - */ - - /** - * The publisher has started streaming to the session. - * @name streamCreated - * @event - * @memberof Publisher - * @see StreamEvent - * @see Session.publish() - */ - - /** - * The publisher has stopped streaming to the session. The default behavior is that - * the Publisher object is removed from the HTML DOM). The Publisher object dispatches a - * destroyed event when the element is removed from the HTML DOM. If you call the - * preventDefault() method of the event object in the event listener, the default - * behavior is prevented, and you can, optionally, retain the Publisher for reuse or clean it up - * using your own code. - * @name streamDestroyed - * @event - * @memberof Publisher - * @see StreamEvent - */ - - /** - * Dispatched when the Publisher element is removed from the HTML DOM. When this event - * is dispatched, you may choose to adjust or remove HTML DOM elements related to the publisher. - * @name destroyed - * @event - * @memberof Publisher - */ + _prevStats = { + 'timeStamp' : OT.$.now() }; - // Helper function to generate unique publisher ids - OT.Publisher.nextId = OT.$.uuid; + OT.$.eventing(this); -})(window); -!(function() { + if(!_isScreenSharing && _audioLevelCapable) { + _audioLevelSampler = new OT.AnalyserAudioLevelSampler(OT.audioContext()); -/** - * The Subscriber object is a representation of the local video element that is playing back - * a remote stream. The Subscriber object includes methods that let you disable and enable - * local audio playback for the subscribed stream. The subscribe() method of the - * {@link Session} object returns a Subscriber object. - * - * @property {Element} element The HTML DOM element containing the Subscriber. - * @property {String} id The DOM ID of the Subscriber. - * @property {Stream} stream The stream to which you are subscribing. - * - * @class Subscriber - * @augments EventDispatcher - */ - OT.Subscriber = function(targetElement, options) { - var _widgetId = OT.$.uuid(), - _domId = targetElement || _widgetId, - _container, - _streamContainer, - _chrome, - _audioLevelMeter, - _stream, - _fromConnectionId, - _peerConnection, - _session = options.session, - _subscribeStartTime, - _startConnectingTime, - _properties = OT.$.clone(options), - _analytics = new OT.Analytics(), - _audioVolume = 100, - _state, - _prevStats, - _lastSubscribeToVideoReason, - _audioLevelCapable = OT.$.hasCapabilities('audioOutputLevelStat') || - OT.$.hasCapabilities('webAudioCapableRemoteStream'), - _audioLevelSampler, - _audioLevelRunner, - _frameRateRestricted = false, - _subscriber = this; - - this.id = _domId; - this.widgetId = _widgetId; - this.session = _session; - - _prevStats = { - timeStamp: OT.$.now() - }; - - if (!_session) { - OT.handleJsException('Subscriber must be passed a session option', 2000, { - session: _session, - target: this + var audioLevelRunner = new OT.IntervalRunner(function() { + _audioLevelSampler.sample(function(audioInputLevel) { + OT.$.requestAnimationFrame(function() { + _publisher.dispatchEvent( + new OT.AudioLevelUpdatedEvent(audioInputLevel)); + }); }); + }, 60); - return; - } - - OT.$.eventing(this, false); - - if(_audioLevelCapable) { - this.on({ - 'audioLevelUpdated:added': function(count) { - if (count === 1 && _audioLevelRunner) { - _audioLevelRunner.start(); - } - }, - 'audioLevelUpdated:removed': function(count) { - if (count === 0 && _audioLevelRunner) { - _audioLevelRunner.stop(); - } + this.on({ + 'audioLevelUpdated:added': function(count) { + if (count === 1) { + audioLevelRunner.start(); } - }); - } - - OT.StylableComponent(this, { - nameDisplayMode: 'auto', - buttonDisplayMode: 'auto', - audioLevelDisplayMode: 'auto', - videoDisabledIndicatorDisplayMode: 'auto', - backgroundImageURI: null, - showArchiveStatus: true, - showMicButton: true + }, + 'audioLevelUpdated:removed': function(count) { + if (count === 0) { + audioLevelRunner.stop(); + } + } }); + } - var logAnalyticsEvent = function(action, variation, payloadType, payload) { - /* jshint camelcase:false*/ - _analytics.logEvent({ - action: action, - variation: variation, - payload_type: payloadType, - payload: payload, - stream_id: _stream ? _stream.id : null, - session_id: _session ? _session.sessionId : null, - connection_id: _session && _session.isConnected() ? - _session.connection.connectionId : null, - partner_id: _session && _session.isConnected() ? _session.sessionInfo.partnerId : null, - widget_id: _widgetId, - widget_type: 'Subscriber' + /// Private Methods + var logAnalyticsEvent = function(action, variation, payload, throttle) { + OT.analytics.logEvent({ + action: action, + variation: variation, + payload: payload, + 'sessionId': _session ? _session.sessionId : null, + 'connectionId': _session && + _session.isConnected() ? _session.connection.connectionId : null, + 'partnerId': _session ? _session.apiKey : OT.APIKEY, + streamId: _stream ? _stream.id : null + }, throttle); + }, + + logConnectivityEvent = function(variation, payload) { + if (variation === 'Attempt' || !_connectivityAttemptPinger) { + _connectivityAttemptPinger = new OT.ConnectivityAttemptPinger({ + action: 'Publish', + 'sessionId': _session ? _session.sessionId : null, + 'connectionId': _session && + _session.isConnected() ? _session.connection.connectionId : null, + 'partnerId': _session ? _session.apiKey : OT.APIKEY, + streamId: _stream ? _stream.id : null }); - }, + } + if (variation === 'Failure' && payload.reason !== 'Non-fatal') { + // We don't want to log an invalid sequence in this case because it was a + // non-fatal failure + _connectivityAttemptPinger.setVariation(variation); + } + logAnalyticsEvent('Publish', variation, payload); + }, - recordQOS = OT.$.bind(function(parsedStats) { - if(_state.isSubscribing() && _session && _session.isConnected()) { - /*jshint camelcase:false */ - var QoSBlob = { - widget_type: 'Subscriber', - stream_type : 'WebRTC', - width: _container ? OT.$.width(_container.domElement) : undefined, - height: _container ? OT.$.height(_container.domElement) : undefined, - session_id: _session ? _session.sessionId : null, - connectionId: _session ? _session.connection.connectionId : null, - media_server_name: _session ? _session.sessionInfo.messagingServer : null, - p2pFlag: _session ? _session.sessionInfo.p2pEnabled : false, - partner_id: _session ? _session.apiKey : null, - stream_id: _stream.id, - widget_id: _widgetId, - version: OT.properties.version, - duration: parseInt(OT.$.now() - _subscribeStartTime, 10), - remote_connection_id: _stream.connection.connectionId - }; + recordQOS = OT.$.bind(function(connection, parsedStats) { + var QoSBlob = { + streamType: 'WebRTC', + sessionId: _session ? _session.sessionId : null, + connectionId: _session && _session.isConnected() ? + _session.connection.connectionId : null, + partnerId: _session ? _session.apiKey : OT.APIKEY, + streamId: _stream ? _stream.id : null, + width: _widgetView ? Number(OT.$.width(_widgetView.domElement).replace('px', '')) + : undefined, + height: _widgetView ? Number(OT.$.height(_widgetView.domElement).replace('px', '')) + : undefined, + version: OT.properties.version, + mediaServerName: _session ? _session.sessionInfo.messagingServer : null, + p2pFlag: _session ? _session.sessionInfo.p2pEnabled : false, + duration: _publishStartTime ? new Date().getTime() - _publishStartTime.getTime() : 0, + remoteConnectionId: connection.id + }; + OT.analytics.logQOS( OT.$.extend(QoSBlob, parsedStats) ); + this.trigger('qos', parsedStats); + }, this), - _analytics.logQOS( OT.$.extend(QoSBlob, parsedStats) ); - this.trigger('qos', parsedStats); - } - }, this), + /// Private Events + + stateChangeFailed = function(changeFailed) { + OT.error('Publisher State Change Failed: ', changeFailed.message); + OT.debug(changeFailed); + }, + + onLoaded = OT.$.bind(function() { + if (_state.isDestroyed()) { + // The publisher was destroyed before loading finished + return; + } + + OT.debug('OT.Publisher.onLoaded'); + + _state.set('MediaBound'); + + // If we have a session and we haven't created the stream yet then + // wait until that is complete before hiding the loading spinner + _widgetView.loading(this.session ? !_stream : false); + + _loaded = true; + + createChrome.call(this); + + this.trigger('initSuccess'); + this.trigger('loaded', this); + }, this), + + onLoadFailure = OT.$.bind(function(reason) { + var errorCode = OT.ExceptionCodes.P2P_CONNECTION_FAILED; + var payload = { + reason: 'Publisher PeerConnection Error: ', + code: errorCode, + message: reason + }; + logConnectivityEvent('Failure', payload); + + _state.set('Failed'); + this.trigger('publishComplete', new OT.Error(errorCode, + 'Publisher PeerConnection Error: ' + reason)); + + OT.handleJsException('Publisher PeerConnection Error: ' + reason, + OT.ExceptionCodes.P2P_CONNECTION_FAILED, { + session: _session, + target: this + }); + }, this), + + onStreamAvailable = OT.$.bind(function(webOTStream) { + OT.debug('OT.Publisher.onStreamAvailable'); + + _state.set('BindingMedia'); + + cleanupLocalStream(); + _webRTCStream = webOTStream; + + _microphone = new OT.Microphone(_webRTCStream, !_properties.publishAudio); + this.publishVideo(_properties.publishVideo && + _webRTCStream.getVideoTracks().length > 0); - stateChangeFailed = function(changeFailed) { - OT.error('Subscriber State Change Failed: ', changeFailed.message); - OT.debug(changeFailed); - }, + this.accessAllowed = true; + this.dispatchEvent(new OT.Event(OT.Event.names.ACCESS_ALLOWED, false)); - onLoaded = function() { - if (_state.isSubscribing() || !_streamContainer) return; + var videoContainerOptions = { + muted: true, + error: onVideoError + }; - OT.debug('OT.Subscriber.onLoaded'); - - _state.set('Subscribing'); - _subscribeStartTime = OT.$.now(); - - logAnalyticsEvent('createPeerConnection', 'Success', 'pcc|hasRelayCandidates', [ - parseInt(_subscribeStartTime - _startConnectingTime, 10), - _peerConnection && _peerConnection.hasRelayCandidates() - ].join('|')); - - _container.loading(false); - - _createChrome.call(this); - if(_frameRateRestricted) { - _stream.setRestrictFrameRate(true); + _targetElement = _widgetView.bindVideo(_webRTCStream, + videoContainerOptions, + function(err) { + if (err) { + onLoadFailure(err); + return; } - this.trigger('subscribeComplete', null, this); - this.trigger('loaded', this); + onLoaded(); + }); - logAnalyticsEvent('subscribe', 'Success', 'streamId', _stream.id); - }, + if(_audioLevelSampler && _webRTCStream.getAudioTracks().length > 0) { + _audioLevelSampler.webRTCStream = _webRTCStream; + } - onDisconnected = function() { - OT.debug('OT.Subscriber has been disconnected from the Publisher\'s PeerConnection'); + }, this), - if (_state.isAttemptingToSubscribe()) { - // subscribing error - _state.set('Failed'); - this.trigger('subscribeComplete', new OT.Error(null, 'ClientDisconnected')); + onStreamAvailableError = OT.$.bind(function(error) { + OT.error('OT.Publisher.onStreamAvailableError ' + error.name + ': ' + error.message); - } else if (_state.isSubscribing()) { - _state.set('Failed'); + _state.set('Failed'); + this.trigger('publishComplete', new OT.Error(OT.ExceptionCodes.UNABLE_TO_PUBLISH, + error.message)); - // we were disconnected after we were already subscribing - // probably do nothing? - } + if (_widgetView) _widgetView.destroy(); - this.disconnect(); - }, + var payload = { + reason: 'GetUserMedia', + code: OT.ExceptionCodes.UNABLE_TO_PUBLISH, + message: 'Publisher failed to access camera/mic: ' + error.message + }; + logConnectivityEvent('Failure', payload); - onPeerConnectionFailure = OT.$.bind(function(reason, peerConnection, prefix) { - if (_state.isAttemptingToSubscribe()) { - // We weren't subscribing yet so this was a failure in setting - // up the PeerConnection or receiving the initial stream. - logAnalyticsEvent('createPeerConnection', 'Failure', 'reason|hasRelayCandidates', [ - 'Subscriber PeerConnection Error: ' + reason, - _peerConnection && _peerConnection.hasRelayCandidates() - ].join('|')); + OT.handleJsException(payload.reason, + payload.code, { + session: _session, + target: this + }); + }, this), - _state.set('Failed'); - this.trigger('subscribeComplete', new OT.Error(null, reason)); + onScreenSharingError = OT.$.bind(function(error) { + OT.error('OT.Publisher.onScreenSharingError ' + error.message); + _state.set('Failed'); - } else if (_state.isSubscribing()) { - // we were disconnected after we were already subscribing - _state.set('Failed'); - this.trigger('error', reason); - } + this.trigger('publishComplete', new OT.Error(OT.ExceptionCodes.UNABLE_TO_PUBLISH, + 'Screensharing: ' + error.message)); - this.disconnect(); + var payload = { + reason: 'ScreenSharing', + message:error.message + }; + logConnectivityEvent('Failure', payload); + }, this), - logAnalyticsEvent('subscribe', 'Failure', 'reason', - (prefix ? prefix : '') + ':Subscriber PeerConnection Error: ' + reason); + // The user has clicked the 'deny' button the the allow access dialog + // (or it's set to always deny) + onAccessDenied = OT.$.bind(function(error) { + OT.error('OT.Publisher.onStreamAvailableError Permission Denied'); - OT.handleJsException('Subscriber PeerConnection Error: ' + reason, - OT.ExceptionCodes.P2P_CONNECTION_FAILED, { - session: _session, - target: this - } + _state.set('Failed'); + var errorMessage = 'Publisher Access Denied: Permission Denied' + + (error.message ? ': ' + error.message : ''); + var errorCode = OT.ExceptionCodes.UNABLE_TO_PUBLISH; + this.trigger('publishComplete', new OT.Error(errorCode, errorMessage)); + + var payload = { + reason: 'GetUserMedia', + code: errorCode, + message: errorMessage + }; + logConnectivityEvent('Failure', payload); + + this.dispatchEvent(new OT.Event(OT.Event.names.ACCESS_DENIED)); + }, this), + + onAccessDialogOpened = OT.$.bind(function() { + logAnalyticsEvent('accessDialog', 'Opened'); + + this.dispatchEvent(new OT.Event(OT.Event.names.ACCESS_DIALOG_OPENED, true)); + }, this), + + onAccessDialogClosed = OT.$.bind(function() { + logAnalyticsEvent('accessDialog', 'Closed'); + + this.dispatchEvent( new OT.Event(OT.Event.names.ACCESS_DIALOG_CLOSED, false)); + }, this), + + onVideoError = OT.$.bind(function(errorCode, errorReason) { + OT.error('OT.Publisher.onVideoError'); + + var message = errorReason + (errorCode ? ' (' + errorCode + ')' : ''); + logAnalyticsEvent('stream', null, {reason:'Publisher while playing stream: ' + message}); + + _state.set('Failed'); + + if (_state.isAttemptingToPublish()) { + this.trigger('publishComplete', new OT.Error(OT.ExceptionCodes.UNABLE_TO_PUBLISH, + message)); + } else { + this.trigger('error', message); + } + + OT.handleJsException('Publisher error playing stream: ' + message, + OT.ExceptionCodes.UNABLE_TO_PUBLISH, { + session: _session, + target: this + }); + }, this), + + onPeerDisconnected = OT.$.bind(function(peerConnection) { + OT.debug('OT.Subscriber has been disconnected from the Publisher\'s PeerConnection'); + + this.cleanupSubscriber(peerConnection.remoteConnection().id); + }, this), + + onPeerConnectionFailure = OT.$.bind(function(code, reason, peerConnection, prefix) { + var payload = { + reason: prefix ? prefix : 'PeerConnectionError', + code: OT.ExceptionCodes.UNABLE_TO_PUBLISH, + message: (prefix ? prefix : '') + ':Publisher PeerConnection with connection ' + + (peerConnection && peerConnection.remoteConnection && + peerConnection.remoteConnection().id) + ' failed: ' + reason, + hasRelayCandidates: peerConnection.hasRelayCandidates() + }; + if (_state.isPublishing()) { + // We're already publishing so this is a Non-fatal failure, must be p2p and one of our + // peerconnections failed + payload.reason = 'Non-fatal'; + } + logConnectivityEvent('Failure', payload); + + OT.handleJsException('Publisher PeerConnection Error: ' + reason, + OT.ExceptionCodes.UNABLE_TO_PUBLISH, { + session: _session, + target: this + }); + + // We don't call cleanupSubscriber as it also logs a + // disconnected analytics event, which we don't want in this + // instance. The duplication is crufty though and should + // be tidied up. + + delete _peerConnections[peerConnection.remoteConnection().id]; + }, this), + + /// Private Helpers + + // Assigns +stream+ to this publisher. The publisher listens + // for a bunch of events on the stream so it can respond to + // changes. + assignStream = OT.$.bind(function(stream) { + this.stream = _stream = stream; + _stream.on('destroyed', this.disconnect, this); + + _state.set('Publishing'); + _widgetView.loading(!_loaded); + _publishStartTime = new Date(); + + this.trigger('publishComplete', null, this); + + this.dispatchEvent(new OT.StreamEvent('streamCreated', stream, null, false)); + + var payload = { + streamType: 'WebRTC', + }; + logConnectivityEvent('Success', payload); + }, this), + + // Clean up our LocalMediaStream + cleanupLocalStream = function() { + if (_webRTCStream) { + // Stop revokes our access cam and mic access for this instance + // of localMediaStream. + _webRTCStream.stop(); + _webRTCStream = null; + } + }, + + createPeerConnectionForRemote = OT.$.bind(function(remoteConnection) { + var peerConnection = _peerConnections[remoteConnection.id]; + + if (!peerConnection) { + var startConnectingTime = OT.$.now(); + + logAnalyticsEvent('createPeerConnection', 'Attempt'); + + // Cleanup our subscriber when they disconnect + remoteConnection.on('destroyed', + OT.$.bind(this.cleanupSubscriber, this, remoteConnection.id)); + + peerConnection = _peerConnections[remoteConnection.id] = new OT.PublisherPeerConnection( + remoteConnection, + _session, + _streamId, + _webRTCStream ); - _showError.call(this, reason); - }, this), - onRemoteStreamAdded = function(webOTStream) { - OT.debug('OT.Subscriber.onRemoteStreamAdded'); - - _state.set('BindingRemoteStream'); - - // Disable the audio/video, if needed - this.subscribeToAudio(_properties.subscribeToAudio); - - _lastSubscribeToVideoReason = 'loading'; - this.subscribeToVideo(_properties.subscribeToVideo, 'loading'); - - var videoContainerOptions = { - error: onPeerConnectionFailure, - audioVolume: _audioVolume - }; - - // This is a workaround for a bug in Chrome where a track disabled on - // the remote end doesn't fire loadedmetadata causing the subscriber to timeout - // https://jira.tokbox.com/browse/OPENTOK-15605 - var browser = OT.$.browserVersion(), - tracks, - reenableVideoTrack = false; - if (!_stream.hasVideo && browser.browser === 'Chrome' && browser.version >= 35) { - tracks = webOTStream.getVideoTracks(); - if(tracks.length > 0) { - tracks[0].enabled = false; - reenableVideoTrack = tracks[0]; - } - } - - _streamContainer = _container.bindVideo(webOTStream, - videoContainerOptions, - OT.$.bind(function(err) { - if (err) { - onPeerConnectionFailure(err.message || err, _peerConnection, 'VideoElement'); - return; - } - - // Continues workaround for https://jira.tokbox.com/browse/OPENTOK-15605 - if (reenableVideoTrack != null && _properties.subscribeToVideo) { - reenableVideoTrack.enabled = true; - } - - _streamContainer.orientation({ - width: _stream.videoDimensions.width, - height: _stream.videoDimensions.height, - videoOrientation: _stream.videoDimensions.orientation - }); - - onLoaded.call(this, null); - }, this)); - - if (OT.$.hasCapabilities('webAudioCapableRemoteStream') && _audioLevelSampler && - webOTStream.getAudioTracks().length > 0) { - _audioLevelSampler.webOTStream = webOTStream; - } - - logAnalyticsEvent('createPeerConnection', 'StreamAdded', '', ''); - this.trigger('streamAdded', this); - }, - - onRemoteStreamRemoved = function(webOTStream) { - OT.debug('OT.Subscriber.onStreamRemoved'); - - if (_streamContainer.stream === webOTStream) { - _streamContainer.destroy(); - _streamContainer = null; - } - - - this.trigger('streamRemoved', this); - }, - - streamDestroyed = function () { - this.disconnect(); - }, - - streamUpdated = function(event) { - - switch(event.changedProperty) { - case 'videoDimensions': - if (!_streamContainer) { - // Ignore videoEmension updates before streamContainer is created OPENTOK-17253 - break; - } - _streamContainer.orientation({ - width: event.newValue.width, - height: event.newValue.height, - videoOrientation: event.newValue.orientation - }); - break; - - case 'videoDisableWarning': - _chrome.videoDisabledIndicator.setWarning(event.newValue); - this.dispatchEvent(new OT.VideoDisableWarningEvent( - event.newValue ? 'videoDisableWarning' : 'videoDisableWarningLifted' - )); - break; - - case 'hasVideo': - - setAudioOnly(!(_stream.hasVideo && _properties.subscribeToVideo)); - - this.dispatchEvent(new OT.VideoEnabledChangedEvent( - _stream.hasVideo ? 'videoEnabled' : 'videoDisabled', { - reason: 'publishVideo' - })); - break; - - case 'hasAudio': - // noop - } - }, - - /// Chrome - - // If mode is false, then that is the mode. If mode is true then we'll - // definitely display the button, but we'll defer the model to the - // Publishers buttonDisplayMode style property. - chromeButtonMode = function(mode) { - if (mode === false) return 'off'; - - var defaultMode = this.getStyle('buttonDisplayMode'); - - // The default model is false, but it's overridden by +mode+ being true - if (defaultMode === false) return 'on'; - - // defaultMode is either true or auto. - return defaultMode; - }, - - updateChromeForStyleChange = function(key, value/*, oldValue*/) { - if (!_chrome) return; - - switch(key) { - case 'nameDisplayMode': - _chrome.name.setDisplayMode(value); - _chrome.backingBar.setNameMode(value); - break; - - case 'videoDisabledDisplayMode': - _chrome.videoDisabledIndicator.setDisplayMode(value); - break; - - case 'showArchiveStatus': - _chrome.archive.setShowArchiveStatus(value); - break; - - case 'buttonDisplayMode': - _chrome.muteButton.setDisplayMode(value); - _chrome.backingBar.setMuteMode(value); - break; - - case 'audioLevelDisplayMode': - _chrome.audioLevel.setDisplayMode(value); - break; - - case 'backgroundImageURI': - _container.setBackgroundImageURI(value); - } - }, - - _createChrome = function() { - - var widgets = { - backingBar: new OT.Chrome.BackingBar({ - nameMode: !_properties.name ? 'off' : this.getStyle('nameDisplayMode'), - muteMode: chromeButtonMode.call(this, this.getStyle('showMuteButton')) - }), - - name: new OT.Chrome.NamePanel({ - name: _properties.name, - mode: this.getStyle('nameDisplayMode') - }), - - muteButton: new OT.Chrome.MuteButton({ - muted: _properties.muted, - mode: chromeButtonMode.call(this, this.getStyle('showMuteButton')) - }), - - archive: new OT.Chrome.Archiving({ - show: this.getStyle('showArchiveStatus'), - archiving: false - }) - }; - - if (_audioLevelCapable) { - var audioLevelTransformer = new OT.AudioLevelTransformer(); - - var audioLevelUpdatedHandler = function(evt) { - _audioLevelMeter.setValue(audioLevelTransformer.transform(evt.audioLevel)); - }; - - _audioLevelMeter = new OT.Chrome.AudioLevelMeter({ - mode: this.getStyle('audioLevelDisplayMode'), - onActivate: function() { - _subscriber.on('audioLevelUpdated', audioLevelUpdatedHandler); - }, - onPassivate: function() { - _subscriber.off('audioLevelUpdated', audioLevelUpdatedHandler); - } - }); - - widgets.audioLevel = _audioLevelMeter; - } - - widgets.videoDisabledIndicator = new OT.Chrome.VideoDisabledIndicator({ - mode: this.getStyle('videoDisabledDisplayMode') - }); - - _chrome = new OT.Chrome({ - parent: _container.domElement - }).set(widgets).on({ - muted: function() { - muteAudio.call(this, true); + peerConnection.on({ + connected: function() { + var payload = { + pcc: parseInt(OT.$.now() - startConnectingTime, 10), + hasRelayCandidates: peerConnection.hasRelayCandidates() + }; + logAnalyticsEvent('createPeerConnection', 'Success', payload); }, - - unmuted: function() { - muteAudio.call(this, false); - } + disconnected: onPeerDisconnected, + error: onPeerConnectionFailure, + qos: recordQOS }, this); - if(_audioLevelMeter && this.getStyle('audioLevelDisplayMode') === 'auto') { - _audioLevelMeter[_container.audioOnly() ? 'show' : 'hide'](); - } - }, + peerConnection.init(_iceServers); + } - _showError = function() { - // Display the error message inside the container, assuming it's - // been created by now. - if (_container) { - _container.addError( - 'The stream was unable to connect due to a network error.', - 'Make sure your connection isn\'t blocked by a firewall.' - ); + return peerConnection; + }, this), + + /// Chrome + + // If mode is false, then that is the mode. If mode is true then we'll + // definitely display the button, but we'll defer the model to the + // Publishers buttonDisplayMode style property. + chromeButtonMode = function(mode) { + if (mode === false) return 'off'; + + var defaultMode = this.getStyle('buttonDisplayMode'); + + // The default model is false, but it's overridden by +mode+ being true + if (defaultMode === false) return 'on'; + + // defaultMode is either true or auto. + return defaultMode; + }, + + updateChromeForStyleChange = function(key, value) { + if (!_chrome) return; + + switch(key) { + case 'nameDisplayMode': + _chrome.name.setDisplayMode(value); + _chrome.backingBar.setNameMode(value); + break; + + case 'showArchiveStatus': + logAnalyticsEvent('showArchiveStatus', 'styleChange', {mode: value ? 'on': 'off'}); + _chrome.archive.setShowArchiveStatus(value); + break; + + case 'buttonDisplayMode': + _chrome.muteButton.setDisplayMode(value); + _chrome.backingBar.setMuteMode(value); + break; + + case 'audioLevelDisplayMode': + _chrome.audioLevel.setDisplayMode(value); + break; + + case 'backgroundImageURI': + _widgetView.setBackgroundImageURI(value); + } + }, + + createChrome = function() { + + if(!this.getStyle('showArchiveStatus')) { + logAnalyticsEvent('showArchiveStatus', 'createChrome', {mode: 'off'}); + } + + var widgets = { + backingBar: new OT.Chrome.BackingBar({ + nameMode: !_properties.name ? 'off' : this.getStyle('nameDisplayMode'), + muteMode: chromeButtonMode.call(this, this.getStyle('buttonDisplayMode')) + }), + + name: new OT.Chrome.NamePanel({ + name: _properties.name, + mode: this.getStyle('nameDisplayMode') + }), + + muteButton: new OT.Chrome.MuteButton({ + muted: _properties.publishAudio === false, + mode: chromeButtonMode.call(this, this.getStyle('buttonDisplayMode')) + }), + + archive: new OT.Chrome.Archiving({ + show: this.getStyle('showArchiveStatus'), + archiving: false + }) + }; + + if (_audioLevelCapable) { + var audioLevelTransformer = new OT.AudioLevelTransformer(); + + var audioLevelUpdatedHandler = function(evt) { + _audioLevelMeter.setValue(audioLevelTransformer.transform(evt.audioLevel)); + }; + + _audioLevelMeter = new OT.Chrome.AudioLevelMeter({ + mode: this.getStyle('audioLevelDisplayMode'), + onActivate: function() { + _publisher.on('audioLevelUpdated', audioLevelUpdatedHandler); + }, + onPassivate: function() { + _publisher.off('audioLevelUpdated', audioLevelUpdatedHandler); + } + }); + + widgets.audioLevel = _audioLevelMeter; + } + + _chrome = new OT.Chrome({ + parent: _widgetView.domElement + }).set(widgets).on({ + muted: OT.$.bind(this.publishAudio, this, false), + unmuted: OT.$.bind(this.publishAudio, this, true) + }); + + if(_audioLevelMeter && this.getStyle('audioLevelDisplayMode') === 'auto') { + _audioLevelMeter[_widgetView.audioOnly() ? 'show' : 'hide'](); + } + }, + + reset = OT.$.bind(function() { + if (_chrome) { + _chrome.destroy(); + _chrome = null; + } + + this.disconnect(); + + _microphone = null; + + if (_targetElement) { + _targetElement.destroy(); + _targetElement = null; + } + + cleanupLocalStream(); + + if (_widgetView) { + _widgetView.destroy(); + _widgetView = null; + } + + if (_session) { + this._.unpublishFromSession(_session, 'reset'); + } + + this.id = _domId = null; + this.stream = _stream = null; + _loaded = false; + + this.session = _session = null; + + if (!_state.isDestroyed()) _state.set('NotPublishing'); + }, this); + + OT.StylableComponent(this, { + showArchiveStatus: true, + nameDisplayMode: 'auto', + buttonDisplayMode: 'auto', + audioLevelDisplayMode: _isScreenSharing ? 'off' : 'auto', + backgroundImageURI: null + }, _properties.showControls, function (payload) { + logAnalyticsEvent('SetStyle', 'Publisher', payload, 0.1); + }); + + var setAudioOnly = function(audioOnly) { + if (_widgetView) { + _widgetView.audioOnly(audioOnly); + _widgetView.showPoster(audioOnly); + } + + if (_audioLevelMeter && _publisher.getStyle('audioLevelDisplayMode') === 'auto') { + _audioLevelMeter[audioOnly ? 'show' : 'hide'](); + } + }; + + this.publish = function(targetElement) { + OT.debug('OT.Publisher: publish'); + + if ( _state.isAttemptingToPublish() || _state.isPublishing() ) reset(); + _state.set('GetUserMedia'); + + if (!_properties.constraints) { + _properties.constraints = OT.$.clone(defaultConstraints); + + if (_isScreenSharing) { + if (_properties.audioSource != null) { + OT.warn('Invalid audioSource passed to Publisher - when using screen sharing no ' + + 'audioSource may be used'); + } + _properties.audioSource = null; + } + + if(_properties.audioSource === null || _properties.audioSource === false) { + _properties.constraints.audio = false; + _properties.publishAudio = false; + } else { + if(typeof _properties.audioSource === 'object') { + if(_properties.audioSource.deviceId != null) { + _properties.audioSource = _properties.audioSource.deviceId; + } else { + OT.warn('Invalid audioSource passed to Publisher. Expected either a device ID'); + } + } + + if (_properties.audioSource) { + if (typeof _properties.constraints.audio !== 'object') { + _properties.constraints.audio = {}; + } + if (!_properties.constraints.audio.mandatory) { + _properties.constraints.audio.mandatory = {}; + } + if (!_properties.constraints.audio.optional) { + _properties.constraints.audio.optional = []; + } + _properties.constraints.audio.mandatory.sourceId = + _properties.audioSource; + } + } + + if(_properties.videoSource === null || _properties.videoSource === false) { + _properties.constraints.video = false; + _properties.publishVideo = false; + } else { + + if(typeof _properties.videoSource === 'object' && + _properties.videoSource.deviceId == null) { + OT.warn('Invalid videoSource passed to Publisher. Expected either a device ' + + 'ID or device.'); + _properties.videoSource = null; + } + + var _setupVideoDefaults = function() { + if (typeof _properties.constraints.video !== 'object') { + _properties.constraints.video = {}; + } + if (!_properties.constraints.video.mandatory) { + _properties.constraints.video.mandatory = {}; + } + if (!_properties.constraints.video.optional) { + _properties.constraints.video.optional = []; } }; - var setAudioOnly = function(audioOnly) { - if(_container) { - _container.audioOnly(audioOnly); - _container.showPoster(audioOnly); - } + if (_properties.videoSource) { + _setupVideoDefaults(); - if (_audioLevelMeter && _subscriber.getStyle('audioLevelDisplayMode') === 'auto') { - _audioLevelMeter[audioOnly ? 'show' : 'hide'](); - } - }; - - this.subscribe = function(stream) { - OT.debug('OT.Subscriber: subscribe to ' + stream.id); - - if (_state.isSubscribing()) { - // @todo error - OT.error('OT.Subscriber.Subscribe: Cannot subscribe, already subscribing.'); - return false; - } - - _state.set('Init'); - - if (!stream) { - // @todo error - OT.error('OT.Subscriber: No stream parameter.'); - return false; - } - - if (_stream) { - // @todo error - OT.error('OT.Subscriber: Already subscribed'); - return false; - } - - this.stream = _stream = stream; - this.streamId = _stream.id; - _stream.on({ - updated: streamUpdated, - destroyed: streamDestroyed - }, this); - - _fromConnectionId = stream.connection.id; - _properties.name = _properties.name || _stream.name; - _properties.classNames = 'OT_root OT_subscriber'; - - if (_properties.style) { - this.setStyle(_properties.style, null, true); - } - if (_properties.audioVolume) { - this.setAudioVolume(_properties.audioVolume); - } - - _properties.subscribeToAudio = OT.$.castToBoolean(_properties.subscribeToAudio, true); - _properties.subscribeToVideo = OT.$.castToBoolean(_properties.subscribeToVideo, true); - - _container = new OT.WidgetView(targetElement, _properties); - this.id = _domId = _container.domId(); - this.element = _container.domElement; - - _startConnectingTime = OT.$.now(); - - if (_stream.connection.id !== _session.connection.id) { - logAnalyticsEvent('createPeerConnection', 'Attempt', '', ''); - - _state.set('ConnectingToPeer'); - - _peerConnection = new OT.SubscriberPeerConnection(_stream.connection, _session, - _stream, this, _properties); - - _peerConnection.on({ - disconnected: onDisconnected, - error: onPeerConnectionFailure, - remoteStreamAdded: onRemoteStreamAdded, - remoteStreamRemoved: onRemoteStreamRemoved, - qos: recordQOS - }, this); - - // initialize the peer connection AFTER we've added the event listeners - _peerConnection.init(); - - if (OT.$.hasCapabilities('audioOutputLevelStat')) { - _audioLevelSampler = new OT.GetStatsAudioLevelSampler(_peerConnection, 'out'); - } else if (OT.$.hasCapabilities('webAudioCapableRemoteStream')) { - _audioLevelSampler = new OT.AnalyserAudioLevelSampler(OT.audioContext()); + var mandatory = _properties.constraints.video.mandatory; + + if(_isScreenSharing) { + // this is handled by the extension helpers + } else if(_properties.videoSource.deviceId != null) { + mandatory.sourceId = _properties.videoSource.deviceId; + } else { + mandatory.sourceId = _properties.videoSource; + } } - if(_audioLevelSampler) { - // sample with interval to minimise disturbance on animation loop but dispatch the - // event with RAF since the main purpose is animation of a meter - _audioLevelRunner = new OT.IntervalRunner(function() { - _audioLevelSampler.sample(function(audioOutputLevel) { - if (audioOutputLevel !== null) { - OT.$.requestAnimationFrame(function() { - _subscriber.dispatchEvent( - new OT.AudioLevelUpdatedEvent(audioOutputLevel)); + if (_properties.resolution) { + if (!_validResolutions.hasOwnProperty(_properties.resolution)) { + OT.warn('Invalid resolution passed to the Publisher. Got: ' + + _properties.resolution + ' expecting one of "' + + OT.$.keys(_validResolutions).join('","') + '"'); + } else { + _properties.videoDimensions = _validResolutions[_properties.resolution]; + _setupVideoDefaults(); + if (OT.$.env.name === 'Chrome') { + _properties.constraints.video.optional = + _properties.constraints.video.optional.concat([ + {minWidth: _properties.videoDimensions.width}, + {maxWidth: _properties.videoDimensions.width}, + {minHeight: _properties.videoDimensions.height}, + {maxHeight: _properties.videoDimensions.height} + ]); + } else { + // This is not supported + } + } + } + + if (_properties.maxResolution) { + _setupVideoDefaults(); + if (_properties.maxResolution.width > 1920) { + OT.warn('Invalid maxResolution passed to the Publisher. maxResolution.width must ' + + 'be less than or equal to 1920'); + _properties.maxResolution.width = 1920; + } + if (_properties.maxResolution.height > 1920) { + OT.warn('Invalid maxResolution passed to the Publisher. maxResolution.height must ' + + 'be less than or equal to 1920'); + _properties.maxResolution.height = 1920; + } + + _properties.videoDimensions = _properties.maxResolution; + + if (OT.$.env.name === 'Chrome') { + _setupVideoDefaults(); + _properties.constraints.video.mandatory.maxWidth = + _properties.videoDimensions.width; + _properties.constraints.video.mandatory.maxHeight = + _properties.videoDimensions.height; + } else { + // This is not suppoted + } + } + + if (_properties.frameRate !== void 0 && + OT.$.arrayIndexOf(_validFrameRates, _properties.frameRate) === -1) { + OT.warn('Invalid frameRate passed to the publisher got: ' + + _properties.frameRate + ' expecting one of ' + _validFrameRates.join(',')); + delete _properties.frameRate; + } else if (_properties.frameRate) { + _setupVideoDefaults(); + _properties.constraints.video.optional = + _properties.constraints.video.optional.concat([ + { minFrameRate: _properties.frameRate }, + { maxFrameRate: _properties.frameRate } + ]); + } + + } + + } else { + OT.warn('You have passed your own constraints not using ours'); + } + + if (_properties.style) { + this.setStyle(_properties.style, null, true); + } + + if (_properties.name) { + _properties.name = _properties.name.toString(); + } + + _properties.classNames = 'OT_root OT_publisher'; + + // Defer actually creating the publisher DOM nodes until we know + // the DOM is actually loaded. + OT.onLoad(function() { + _widgetView = new OT.WidgetView(targetElement, _properties); + _publisher.id = _domId = _widgetView.domId(); + _publisher.element = _widgetView.domElement; + + _widgetView.on('videoDimensionsChanged', function(oldValue, newValue) { + if (_stream) { + _stream.setVideoDimensions(newValue.width, newValue.height); + } + _publisher.dispatchEvent( + new OT.VideoDimensionsChangedEvent(_publisher, oldValue, newValue) + ); + }); + + _widgetView.on('mediaStopped', function() { + var event = new OT.MediaStoppedEvent(_publisher); + + _publisher.dispatchEvent(event, function() { + if(!event.isDefaultPrevented()) { + if (_session) { + _publisher._.unpublishFromSession(_session, 'mediaStopped'); + } else { + _publisher.destroy('mediaStopped'); + } + } + }); + }); + + OT.$.waterfall([ + function(cb) { + if (_isScreenSharing) { + OT.checkScreenSharingCapability(function(response) { + if (!response.supported) { + onScreenSharingError( + new Error('Screen Sharing is not supported in this browser') + ); + } else if (response.extensionRegistered === false) { + onScreenSharingError( + new Error('Screen Sharing suppor in this browser requires an extension, but ' + + 'one has not been registered.') + ); + } else if (response.extensionInstalled === false) { + onScreenSharingError( + new Error('Screen Sharing suppor in this browser requires an extension, but ' + + 'the extension is not installed.') + ); + } else { + + var helper = OT.pickScreenSharingHelper(); + + if (helper.proto.getConstraintsShowsPermissionUI) { + onAccessDialogOpened(); + } + + helper.instance.getConstraints(options.videoSource, _properties.constraints, + function(err, constraints) { + if (helper.proto.getConstraintsShowsPermissionUI) { + onAccessDialogClosed(); + } + if (err) { + if (err.message === 'PermissionDeniedError') { + onAccessDenied(err); + } else { + onScreenSharingError(err); + } + } else { + _properties.constraints = constraints; + cb(); + } }); } }); - }, 60); - } - } else { - logAnalyticsEvent('createPeerConnection', 'Attempt', '', ''); - - var publisher = _session.getPublisherForStream(_stream); - if(!(publisher && publisher._.webRtcStream())) { - this.trigger('subscribeComplete', new OT.Error(null, 'InvalidStreamID')); - return this; - } - - // Subscribe to yourself edge-case - onRemoteStreamAdded.call(this, publisher._.webRtcStream()); - } - - logAnalyticsEvent('subscribe', 'Attempt', 'streamId', _stream.id); - - return this; - }; - - this.destroy = function(reason, quiet) { - if (_state.isDestroyed()) return; - - if(reason === 'streamDestroyed') { - if (_state.isAttemptingToSubscribe()) { - // We weren't subscribing yet so the stream was destroyed before we setup - // the PeerConnection or receiving the initial stream. - this.trigger('subscribeComplete', new OT.Error(null, 'InvalidStreamID')); - } - } - - _state.set('Destroyed'); - - if(_audioLevelRunner) { - _audioLevelRunner.stop(); - } - - this.disconnect(); - - if (_chrome) { - _chrome.destroy(); - _chrome = null; - } - - if (_container) { - _container.destroy(); - _container = null; - this.element = null; - } - - if (_stream && !_stream.destroyed) { - logAnalyticsEvent('unsubscribe', null, 'streamId', _stream.id); - } - - this.id = _domId = null; - this.stream = _stream = null; - this.streamId = null; - - this.session =_session = null; - _properties = null; - - if (quiet !== true) { - this.dispatchEvent( - new OT.DestroyedEvent( - OT.Event.names.SUBSCRIBER_DESTROYED, - this, - reason - ), - OT.$.bind(this.off, this) - ); - } - - return this; - }; - - this.disconnect = function() { - if (!_state.isDestroyed() && !_state.isFailed()) { - // If we are already in the destroyed state then disconnect - // has been called after (or from within) destroy. - _state.set('NotSubscribing'); - } - - if (_streamContainer) { - _streamContainer.destroy(); - _streamContainer = null; - } - - if (_peerConnection) { - _peerConnection.destroy(); - _peerConnection = null; - - logAnalyticsEvent('disconnect', 'PeerConnection', 'streamId', _stream.id); - } - }; - - this.processMessage = function(type, fromConnection, message) { - OT.debug('OT.Subscriber.processMessage: Received ' + type + ' message from ' + - fromConnection.id); - OT.debug(message); - - if (_fromConnectionId !== fromConnection.id) { - _fromConnectionId = fromConnection.id; - } - - if (_peerConnection) { - _peerConnection.processMessage(type, message); - } - }; - - this.disableVideo = function(active) { - if (!active) { - OT.warn('Due to high packet loss and low bandwidth, video has been disabled'); - } else { - if (_lastSubscribeToVideoReason === 'auto') { - OT.info('Video has been re-enabled'); - _chrome.videoDisabledIndicator.disableVideo(false); - } else { - OT.info('Video was not re-enabled because it was manually disabled'); - return; - } - } - this.subscribeToVideo(active, 'auto'); - if(!active) { - _chrome.videoDisabledIndicator.disableVideo(true); - } - logAnalyticsEvent('updateQuality', 'video', active ? 'videoEnabled' : 'videoDisabled', true); - }; - - /** - * Return the base-64-encoded string of PNG data representing the Subscriber video. - * - *

You can use the string as the value for a data URL scheme passed to the src parameter of - * an image file, as in the following:

- * - *
-     *  var imgData = subscriber.getImgData();
-     *
-     *  var img = document.createElement("img");
-     *  img.setAttribute("src", "data:image/png;base64," + imgData);
-     *  var imgWin = window.open("about:blank", "Screenshot");
-     *  imgWin.document.write("<body></body>");
-     *  imgWin.document.body.appendChild(img);
-     *  
- * @method #getImgData - * @memberOf Subscriber - * @return {String} The base-64 encoded string. Returns an empty string if there is no video. - */ - this.getImgData = function() { - if (!this.isSubscribing()) { - OT.error('OT.Subscriber.getImgData: Cannot getImgData before the Subscriber ' + - 'is subscribing.'); - return null; - } - - return _streamContainer.imgData(); - }; - - /** - * Sets the audio volume, between 0 and 100, of the Subscriber. - * - *

You can set the initial volume when you call the Session.subscribe() - * method. Pass a audioVolume property of the properties parameter - * of the method.

- * - * @param {Number} value The audio volume, between 0 and 100. - * - * @return {Subscriber} The Subscriber object. This lets you chain method calls, as in the - * following: - * - *
mySubscriber.setAudioVolume(50).setStyle(newStyle);
- * - * @see getAudioVolume() - * @see Session.subscribe() - * @method #setAudioVolume - * @memberOf Subscriber - */ - this.setAudioVolume = function(value) { - value = parseInt(value, 10); - if (isNaN(value)) { - OT.error('OT.Subscriber.setAudioVolume: value should be an integer between 0 and 100'); - return this; - } - _audioVolume = Math.max(0, Math.min(100, value)); - if (_audioVolume !== value) { - OT.warn('OT.Subscriber.setAudioVolume: value should be an integer between 0 and 100'); - } - if(_properties.muted && _audioVolume > 0) { - _properties.premuteVolume = value; - muteAudio.call(this, false); - } - if (_streamContainer) { - _streamContainer.setAudioVolume(_audioVolume); - } - return this; - }; - - /** - * Returns the audio volume, between 0 and 100, of the Subscriber. - * - *

Generally you use this method in conjunction with the setAudioVolume() - * method.

- * - * @return {Number} The audio volume, between 0 and 100, of the Subscriber. - * @see setAudioVolume() - * @method #getAudioVolume - * @memberOf Subscriber - */ - this.getAudioVolume = function() { - if(_properties.muted) { - return 0; - } - if (_streamContainer) return _streamContainer.getAudioVolume(); - else return _audioVolume; - }; - - /** - * Toggles audio on and off. Starts subscribing to audio (if it is available and currently - * not being subscribed to) when the value is true; stops - * subscribing to audio (if it is currently being subscribed to) when the value - * is false. - *

- * Note: This method only affects the local playback of audio. It has no impact on the - * audio for other connections subscribing to the same stream. If the Publsher is not - * publishing audio, enabling the Subscriber audio will have no practical effect. - *

- * - * @param {Boolean} value Whether to start subscribing to audio (true) or not - * (false). - * - * @return {Subscriber} The Subscriber object. This lets you chain method calls, as in the - * following: - * - *
mySubscriber.subscribeToAudio(true).subscribeToVideo(false);
- * - * @see subscribeToVideo() - * @see Session.subscribe() - * @see StreamPropertyChangedEvent - * - * @method #subscribeToAudio - * @memberOf Subscriber - */ - this.subscribeToAudio = function(pValue) { - var value = OT.$.castToBoolean(pValue, true); - - if (_peerConnection) { - _peerConnection.subscribeToAudio(value && !_properties.subscribeMute); - - if (_session && _stream && value !== _properties.subscribeToAudio) { - _stream.setChannelActiveState('audio', value && !_properties.subscribeMute); - } - } - - _properties.subscribeToAudio = value; - - return this; - }; - - var muteAudio = function(_mute) { - _chrome.muteButton.muted(_mute); - - if(_mute === _properties.mute) { - return; - } - if(OT.$.browser() === 'Chrome' || TBPlugin.isInstalled()) { - _properties.subscribeMute = _properties.muted = _mute; - this.subscribeToAudio(_properties.subscribeToAudio); - } else { - if(_mute) { - _properties.premuteVolume = this.getAudioVolume(); - _properties.muted = true; - this.setAudioVolume(0); - } else if(_properties.premuteVolume || _properties.audioVolume) { - _properties.muted = false; - this.setAudioVolume(_properties.premuteVolume || _properties.audioVolume); - } - } - _properties.mute = _properties.mute; - }; - - var reasonMap = { - auto: 'quality', - publishVideo: 'publishVideo', - subscribeToVideo: 'subscribeToVideo' - }; - - - /** - * Toggles video on and off. Starts subscribing to video (if it is available and - * currently not being subscribed to) when the value is true; - * stops subscribing to video (if it is currently being subscribed to) when the - * value is false. - *

- * Note: This method only affects the local playback of video. It has no impact on - * the video for other connections subscribing to the same stream. If the Publsher is not - * publishing video, enabling the Subscriber video will have no practical video. - *

- * - * @param {Boolean} value Whether to start subscribing to video (true) or not - * (false). - * - * @return {Subscriber} The Subscriber object. This lets you chain method calls, as in the - * following: - * - *
mySubscriber.subscribeToVideo(true).subscribeToAudio(false);
- * - * @see subscribeToAudio() - * @see Session.subscribe() - * @see StreamPropertyChangedEvent - * - * @method #subscribeToVideo - * @memberOf Subscriber - */ - this.subscribeToVideo = function(pValue, reason) { - var value = OT.$.castToBoolean(pValue, true); - - setAudioOnly(!(value && _stream.hasVideo)); - - if ( value && _container && _container.video()) { - _container.loading(value); - _container.video().whenTimeIncrements(function() { - _container.loading(false); - }, this); - } - - if (_chrome && _chrome.videoDisabledIndicator) { - _chrome.videoDisabledIndicator.disableVideo(false); - } - - if (_peerConnection) { - _peerConnection.subscribeToVideo(value); - - if (_session && _stream && (value !== _properties.subscribeToVideo || - reason !== _lastSubscribeToVideoReason)) { - _stream.setChannelActiveState('video', value, reason); - } - } - - _properties.subscribeToVideo = value; - _lastSubscribeToVideoReason = reason; - - if (reason !== 'loading') { - this.dispatchEvent(new OT.VideoEnabledChangedEvent( - value ? 'videoEnabled' : 'videoDisabled', - { - reason: reasonMap[reason] || 'subscribeToVideo' - } - )); - } - - return this; - }; - - this.isSubscribing = function() { - return _state.isSubscribing(); - }; - - this.isWebRTC = true; - - this.isLoading = function() { - return _container && _container.loading(); - }; - - this.videoWidth = function() { - return _streamContainer.videoWidth(); - }; - - this.videoHeight = function() { - return _streamContainer.videoHeight(); - }; - - /** - * Restricts the frame rate of the Subscriber's video stream, when you pass in - * true. When you pass in false, the frame rate of the video stream - * is not restricted. - *

- * When the frame rate is restricted, the Subscriber video frame will update once or less per - * second. - *

- * This feature is only available in sessions that use the OpenTok Media Router (sessions with - * the media mode - * set to routed), not in sessions with the media mode set to relayed. In relayed sessions, - * calling this method has no effect. - *

- * Restricting the subscriber frame rate has the following benefits: - *

    - *
  • It reduces CPU usage.
  • - *
  • It reduces the network bandwidth consumed.
  • - *
  • It lets you subscribe to more streams simultaneously.
  • - *
- *

- * Reducing a subscriber's frame rate has no effect on the frame rate of the video in - * other clients. - * - * @param {Boolean} value Whether to restrict the Subscriber's video frame rate - * (true) or not (false). - * - * @return {Subscriber} The Subscriber object. This lets you chain method calls, as in the - * following: - * - *

mySubscriber.restrictFrameRate(false).subscribeToAudio(true);
- * - * @method #restrictFrameRate - * @memberOf Subscriber - */ - this.restrictFrameRate = function(val) { - OT.debug('OT.Subscriber.restrictFrameRate(' + val + ')'); - - logAnalyticsEvent('restrictFrameRate', val.toString(), 'streamId', _stream.id); - - if (_session.sessionInfo.p2pEnabled) { - OT.warn('OT.Subscriber.restrictFrameRate: Cannot restrictFrameRate on a P2P session'); - } - - if (typeof val !== 'boolean') { - OT.error('OT.Subscriber.restrictFrameRate: expected a boolean value got a ' + typeof val); - } else { - _frameRateRestricted = val; - _stream.setRestrictFrameRate(val); - } - return this; - }; - - this.on('styleValueChanged', updateChromeForStyleChange, this); - - this._ = { - archivingStatus: function(status) { - if(_chrome) { - _chrome.archive.setArchiving(status); - } - } - }; - - _state = new OT.SubscribingState(stateChangeFailed); - - /** - * Dispatched periodically to indicate the subscriber's audio level. The event is dispatched - * up to 60 times per second, depending on the browser. The audioLevel property - * of the event is audio level, from 0 to 1.0. See {@link AudioLevelUpdatedEvent} for more - * information. - *

- * The following example adjusts the value of a meter element that shows volume of the - * subscriber. Note that the audio level is adjusted logarithmically and a moving average - * is applied: - *

-   * var movingAvg = null;
-   * subscriber.on('audioLevelUpdated', function(event) {
-   *   if (movingAvg === null || movingAvg <= event.audioLevel) {
-   *     movingAvg = event.audioLevel;
-   *   } else {
-   *     movingAvg = 0.7 * movingAvg + 0.3 * event.audioLevel;
-   *   }
-   *
-   *   // 1.5 scaling to map the -30 - 0 dBm range to [0,1]
-   *   var logLevel = (Math.log(movingAvg) / Math.LN10) / 1.5 + 1;
-   *   logLevel = Math.min(Math.max(logLevel, 0), 1);
-   *   document.getElementById('subscriberMeter').value = logLevel;
-   * });
-   * 
- *

This example shows the algorithm used by the default audio level indicator displayed - * in an audio-only Subscriber. - * - * @name audioLevelUpdated - * @event - * @memberof Subscriber - * @see AudioLevelUpdatedEvent - */ - - /** - * Dispatched when the video for the subscriber is disabled. - *

- * The reason property defines the reason the video was disabled. This can be set to - * one of the following values: - *

- * - *

    - * - *
  • "publishVideo" — The publisher stopped publishing video by calling - * publishVideo(false).
  • - * - *
  • "quality" — The OpenTok Media Router stopped sending video - * to the subscriber based on stream quality changes. This feature of the OpenTok Media - * Router has a subscriber drop the video stream when connectivity degrades. (The subscriber - * continues to receive the audio stream, if there is one.) - *

    - * Before sending this event, when the Subscriber's stream quality deteriorates to a level - * that is low enough that the video stream is at risk of being disabled, the Subscriber - * dispatches a videoDisableWarning event. - *

    - * If connectivity improves to support video again, the Subscriber object dispatches - * a videoEnabled event, and the Subscriber resumes receiving video. - *

    - * By default, the Subscriber displays a video disabled indicator when a - * videoDisabled event with this reason is dispatched and removes the indicator - * when the videoDisabled event with this reason is dispatched. You can control - * the display of this icon by calling the setStyle() method of the Subscriber, - * setting the videoDisabledDisplayMode property(or you can set the style when - * calling the Session.subscribe() method, setting the style property - * of the properties parameter). - *

    - * This feature is only available in sessions that use the OpenTok Media Router (sessions with - * the media mode - * set to routed), not in sessions with the media mode set to relayed. - *

  • - * - *
  • "subscribeToVideo" — The subscriber started or stopped subscribing to - * video, by calling subscribeToVideo(false). - *
  • - * - *
- * - * @see VideoEnabledChangedEvent - * @see event:videoDisableWarning - * @see event:videoEnabled - * @name videoDisabled - * @event - * @memberof Subscriber - */ - - /** - * Dispatched when the OpenTok Media Router determines that the stream quality has degraded - * and the video will be disabled if the quality degrades more. If the quality degrades further, - * the Subscriber disables the video and dispatches a videoDisabled event. - *

- * By default, the Subscriber displays a video disabled warning indicator when this event - * is dispatched (and the video is disabled). You can control the display of this icon by - * calling the setStyle() method and setting the - * videoDisabledDisplayMode property (or you can set the style when calling - * the Session.subscribe() method and setting the style property - * of the properties parameter). - *

- * This feature is only available in sessions that use the OpenTok Media Router (sessions with - * the media mode - * set to routed), not in sessions with the media mode set to relayed. - * - * @see Event - * @see event:videoDisabled - * @see event:videoDisableWarningLifted - * @name videoDisableWarning - * @event - * @memberof Subscriber - */ - - /** - * Dispatched when the OpenTok Media Router determines that the stream quality has improved - * to the point at which the video being disabled is not an immediate risk. This event is - * dispatched after the Subscriber object dispatches a videoDisableWarning event. - *

- * This feature is only available in sessions that use the OpenTok Media Router (sessions with - * the media mode - * set to routed), not in sessions with the media mode set to relayed. - * - * @see Event - * @see event:videoDisabled - * @see event:videoDisableWarning - * @name videoDisableWarningLifted - * @event - * @memberof Subscriber - */ - - /** - * Dispatched when the OpenTok Media Router resumes sending video to the subscriber - * after video was previously disabled. - *

- * The reason property defines the reason the video was enabled. This can be set to - * one of the following values: - *

- * - *

    - * - *
  • "publishVideo" — The publisher started publishing video by calling - * publishVideo(true).
  • - * - *
  • "quality" — The OpenTok Media Router resumed sending video - * to the subscriber based on stream quality changes. This feature of the OpenTok Media - * Router has a subscriber drop the video stream when connectivity degrades and then resume - * the video stream if the stream quality improves. - *

    - * This feature is only available in sessions that use the OpenTok Media Router (sessions with - * the media mode - * set to routed), not in sessions with the media mode set to relayed. - *

  • - * - *
  • "subscribeToVideo" — The subscriber started or stopped subscribing to - * video, by calling subscribeToVideo(false). - *
  • - * - *
- * - *

- * To prevent video from resuming, in the videoEnabled event listener, - * call subscribeToVideo(false) on the Subscriber object. - * - * @see VideoEnabledChangedEvent - * @see event:videoDisabled - * @name videoEnabled - * @event - * @memberof Subscriber - */ - - /** - * Dispatched when the Subscriber element is removed from the HTML DOM. When this event is - * dispatched, you may choose to adjust or remove HTML DOM elements related to the subscriber. - * @see Event - * @name destroyed - * @event - * @memberof Subscriber - */ - }; - -})(window); -!(function() { - - var parseErrorFromJSONDocument, - onGetResponseCallback, - onGetErrorCallback; - - OT.SessionInfo = function(jsonDocument) { - var sessionJSON = jsonDocument[0]; - - OT.log('SessionInfo Response:'); - OT.log(jsonDocument); - - /*jshint camelcase:false*/ - - this.sessionId = sessionJSON.session_id; - this.partnerId = sessionJSON.partner_id; - this.sessionStatus = sessionJSON.session_status; - - this.messagingServer = sessionJSON.messaging_server_url; - - this.messagingURL = sessionJSON.messaging_url; - this.symphonyAddress = sessionJSON.symphony_address; - - this.p2pEnabled = !!(sessionJSON.properties && - sessionJSON.properties.p2p && - sessionJSON.properties.p2p.preference && - sessionJSON.properties.p2p.preference.value === 'enabled'); - }; - - // Retrieves Session Info for +session+. The SessionInfo object will be passed - // to the +onSuccess+ callback. The +onFailure+ callback will be passed an error - // object and the DOMEvent that relates to the error. - OT.SessionInfo.get = function(session, onSuccess, onFailure) { - var sessionInfoURL = OT.properties.apiURL + '/session/' + session.id + '?extended=true', - - browser = OT.$.browserVersion(), - - startTime = OT.$.now(), - - options, - - validateRawSessionInfo = function(sessionInfo) { - session.logEvent('Instrumentation', null, 'gsi', OT.$.now() - startTime); - var error = parseErrorFromJSONDocument(sessionInfo); - if (error === false) { - onGetResponseCallback(session, onSuccess, sessionInfo); } else { - onGetErrorCallback(session, onFailure, error, JSON.stringify(sessionInfo)); + OT.$.shouldAskForDevices(function(devices) { + if(!devices.video) { + OT.warn('Setting video constraint to false, there are no video sources'); + _properties.constraints.video = false; + } + if(!devices.audio) { + OT.warn('Setting audio constraint to false, there are no audio sources'); + _properties.constraints.audio = false; + } + cb(); + }); } - }; + }, + function() { - if(browser.browser === 'IE' && browser.version < 10) { - sessionInfoURL = sessionInfoURL + '&format=json&token=' + encodeURIComponent(session.token) + - '&version=1&cache=' + OT.$.uuid(); - options = { - xdomainrequest: true - }; - } - else { - options = { - headers: { - 'X-TB-TOKEN-AUTH': session.token, - 'X-TB-VERSION': 1 + if (_state.isDestroyed()) { + return; + } + + OT.$.getUserMedia( + _properties.constraints, + onStreamAvailable, + onStreamAvailableError, + onAccessDialogOpened, + onAccessDialogClosed, + onAccessDenied + ); } - }; + + ]); + + }, this); + + return this; + }; + +/** +* Starts publishing audio (if it is currently not being published) +* when the value is true; stops publishing audio +* (if it is currently being published) when the value is false. +* +* @param {Boolean} value Whether to start publishing audio (true) +* or not (false). +* +* @see OT.initPublisher() +* @see Stream.hasAudio +* @see StreamPropertyChangedEvent +* @method #publishAudio +* @memberOf Publisher +*/ + this.publishAudio = function(value) { + _properties.publishAudio = value; + + if (_microphone) { + _microphone.muted(!value); } - session.logEvent('getSessionInfo', 'Attempt', 'api_url', OT.properties.apiURL); - - OT.$.getJSON(sessionInfoURL, options, function(error, sessionInfo) { - if(error) { - var responseText = sessionInfo; - onGetErrorCallback(session, onFailure, - new OT.Error(error.target && error.target.status || error.code, error.message || - 'Could not connect to the OpenTok API Server.'), responseText); - } else { - validateRawSessionInfo(sessionInfo); - } - }); - }; - - var messageServerToClientErrorCodes = {}; - messageServerToClientErrorCodes['404'] = OT.ExceptionCodes.INVALID_SESSION_ID; - messageServerToClientErrorCodes['409'] = OT.ExceptionCodes.INVALID_SESSION_ID; - messageServerToClientErrorCodes['400'] = OT.ExceptionCodes.INVALID_SESSION_ID; - messageServerToClientErrorCodes['403'] = OT.ExceptionCodes.AUTHENTICATION_ERROR; - - // Return the error in +jsonDocument+, if there is one. Otherwise it will return - // false. - parseErrorFromJSONDocument = function(jsonDocument) { - if(OT.$.isArray(jsonDocument)) { - - var errors = OT.$.filter(jsonDocument, function(node) { - return node.error != null; - }); - - var numErrorNodes = errors.length; - if(numErrorNodes === 0) { - return false; - } - - var errorCode = errors[0].error.code; - if (messageServerToClientErrorCodes[errorCode.toString()]) { - errorCode = messageServerToClientErrorCodes[errorCode]; - } - - return { - code: errorCode, - message: errors[0].error.errorMessage && errors[0].error.errorMessage.message - }; - } else { - return { - code: null, - message: 'Unknown error: getSessionInfo JSON response was badly formed' - }; + if (_chrome) { + _chrome.muteButton.muted(!value); } + + if (_session && _stream) { + _stream.setChannelActiveState('audio', value); + } + + return this; }; - onGetResponseCallback = function(session, onSuccess, rawSessionInfo) { - session.logEvent('getSessionInfo', 'Success', 'api_url', OT.properties.apiURL); - - onSuccess( new OT.SessionInfo(rawSessionInfo) ); - }; - - onGetErrorCallback = function(session, onFailure, error, responseText) { - session.logEvent('Connect', 'Failure', 'errorMessage', - 'GetSessionInfo:' + (error.code || 'No code') + ':' + error.message + ':' + - (responseText || 'Empty responseText from API server')); - - onFailure(error, session); - }; - -})(window); -!(function() { - /** - * A class defining properties of the capabilities property of a - * Session object. See Session.capabilities. - *

- * All Capabilities properties are undefined until you have connected to a session - * and the Session object has dispatched the sessionConnected event. - *

- * For more information on token roles, see the - * generate_token() - * method of the OpenTok server-side libraries. - * - * @class Capabilities - * - * @property {Number} forceDisconnect Specifies whether you can call - * the Session.forceDisconnect() method (1) or not (0). To call the - * Session.forceDisconnect() method, - * the user must have a token that is assigned the role of moderator. - * @property {Number} forceUnpublish Specifies whether you can call - * the Session.forceUnpublish() method (1) or not (0). To call the - * Session.forceUnpublish() method, the user must have a token that - * is assigned the role of moderator. - * @property {Number} publish Specifies whether you can publish to the session (1) or not (0). - * The ability to publish is based on a few factors. To publish, the user must have a token that - * is assigned a role that supports publishing. There must be a connected camera and microphone. - * @property {Number} subscribe Specifies whether you can subscribe to streams - * in the session (1) or not (0). Currently, this capability is available for all users on all - * platforms. - */ - OT.Capabilities = function(permissions) { - this.publish = OT.$.arrayIndexOf(permissions, 'publish') !== -1 ? 1 : 0; - this.subscribe = OT.$.arrayIndexOf(permissions, 'subscribe') !== -1 ? 1 : 0; - this.forceUnpublish = OT.$.arrayIndexOf(permissions, 'forceunpublish') !== -1 ? 1 : 0; - this.forceDisconnect = OT.$.arrayIndexOf(permissions, 'forcedisconnect') !== -1 ? 1 : 0; - this.supportsWebRTC = OT.$.hasCapabilities('webrtc') ? 1 : 0; - - this.permittedTo = function(action) { - return this.hasOwnProperty(action) && this[action] === 1; - }; - }; - -})(window); -!(function(window) { - /** - * The Session object returned by the OT.initSession() method provides access to - * much of the OpenTok functionality. - * - * @class Session - * @augments EventDispatcher - * - * @property {Capabilities} capabilities A {@link Capabilities} object that includes information - * about the capabilities of the client. All properties of the capabilities object - * are undefined until you have connected to a session and the Session object has dispatched the - * sessionConnected event. - * @property {Connection} connection The {@link Connection} object for this session. The - * connection property is only available once the Session object dispatches the sessionConnected - * event. The Session object asynchronously dispatches a sessionConnected event in response - * to a successful call to the connect() method. See: connect and - * {@link Connection}. - * @property {String} sessionId The session ID for this session. You pass this value into the - * OT.initSession() method when you create the Session object. (Note: a Session - * object is not connected to the OpenTok server until you call the connect() method of the - * object and the object dispatches a connected event. See {@link OT.initSession} and - * {@link connect}). - * For more information on sessions and session IDs, see - * Session creation. - */ - OT.Session = function(apiKey, sessionId) { - OT.$.eventing(this); - - // Check that the client meets the minimum requirements, if they don't the upgrade - // flow will be triggered. - if (!OT.checkSystemRequirements()) { - OT.upgradeSystemRequirements(); - return; - } - - if(sessionId == null) { - sessionId = apiKey; - apiKey = null; - } - - this.id = this.sessionId = sessionId; - - var _initialConnection = true, - _apiKey = apiKey, - _token, - _sessionId = sessionId, - _socket, - _widgetId = OT.$.uuid(), - _connectionId, - _analytics = new OT.Analytics(), - sessionConnectFailed, - sessionDisconnectedHandler, - connectionCreatedHandler, - connectionDestroyedHandler, - streamCreatedHandler, - streamPropertyModifiedHandler, - streamDestroyedHandler, - archiveCreatedHandler, - archiveDestroyedHandler, - archiveUpdatedHandler, - reset, - disconnectComponents, - destroyPublishers, - destroySubscribers, - connectMessenger, - getSessionInfo, - onSessionInfoResponse, - permittedTo, - dispatchError; - - - - var setState = OT.$.statable(this, [ - 'disconnected', 'connecting', 'connected', 'disconnecting' - ], 'disconnected'); - - this.connection = null; - this.connections = new OT.Collection(); - this.streams = new OT.Collection(); - this.archives = new OT.Collection(); - - - //-------------------------------------- - // MESSAGE HANDLERS - //-------------------------------------- - - // The duplication of this and sessionConnectionFailed will go away when - // session and messenger are refactored - sessionConnectFailed = function(reason, code) { - setState('disconnected'); - - OT.error(reason); - - this.trigger('sessionConnectFailed', - new OT.Error(code || OT.ExceptionCodes.CONNECT_FAILED, reason)); - - OT.handleJsException(reason, code || OT.ExceptionCodes.CONNECT_FAILED, { - session: this - }); - }; - - sessionDisconnectedHandler = function(event) { - var reason = event.reason; - if(reason === 'networkTimedout') { - reason = 'networkDisconnected'; - this.logEvent('Connect', 'TimeOutDisconnect', 'reason', event.reason); - } else { - this.logEvent('Connect', 'Disconnected', 'reason', event.reason); - } - - var publicEvent = new OT.SessionDisconnectEvent('sessionDisconnected', reason); - - reset.call(this); - disconnectComponents.call(this, reason); - - var defaultAction = OT.$.bind(function() { - // Publishers handle preventDefault'ing themselves - destroyPublishers.call(this, publicEvent.reason); - // Subscriers don't, destroy 'em if needed - if (!publicEvent.isDefaultPrevented()) destroySubscribers.call(this, publicEvent.reason); - }, this); - - this.dispatchEvent(publicEvent, defaultAction); - }; - - connectionCreatedHandler = function(connection) { - // We don't broadcast events for the symphony connection - if (connection.id.match(/^symphony\./)) return; - - this.dispatchEvent(new OT.ConnectionEvent( - OT.Event.names.CONNECTION_CREATED, - connection - )); - }; - - connectionDestroyedHandler = function(connection, reason) { - // We don't broadcast events for the symphony connection - if (connection.id.match(/^symphony\./)) return; - - // Don't delete the connection if it's ours. This only happens when - // we're about to receive a session disconnected and session disconnected - // will also clean up our connection. - if (connection.id === _socket.id()) return; - - this.dispatchEvent( - new OT.ConnectionEvent( - OT.Event.names.CONNECTION_DESTROYED, - connection, - reason - ) - ); - }; - - streamCreatedHandler = function(stream) { - if(stream.connection.id !== this.connection.id) { - this.dispatchEvent(new OT.StreamEvent( - OT.Event.names.STREAM_CREATED, - stream, - null, - false - )); - } - }; - - streamPropertyModifiedHandler = function(event) { - var stream = event.target, - propertyName = event.changedProperty, - newValue = event.newValue; - - if (propertyName === 'videoDisableWarning' || propertyName === 'audioDisableWarning') { - return; // These are not public properties, skip top level event for them. - } - - if (propertyName === 'orientation') { - propertyName = 'videoDimensions'; - newValue = {width: newValue.width, height: newValue.height}; - } - - this.dispatchEvent(new OT.StreamPropertyChangedEvent( - OT.Event.names.STREAM_PROPERTY_CHANGED, - stream, - propertyName, - event.oldValue, - newValue - )); - }; - - streamDestroyedHandler = function(stream, reason) { - - // if the stream is one of ours we delegate handling - // to the publisher itself. - if(stream.connection.id === this.connection.id) { - OT.$.forEach(OT.publishers.where({ streamId: stream.id }), OT.$.bind(function(publisher) { - publisher._.unpublishFromSession(this, reason); - }, this)); - return; - } - - var event = new OT.StreamEvent('streamDestroyed', stream, reason, true); - - var defaultAction = OT.$.bind(function() { - if (!event.isDefaultPrevented()) { - // If we are subscribed to any of the streams we should unsubscribe - OT.$.forEach(OT.subscribers.where({streamId: stream.id}), function(subscriber) { - if (subscriber.session.id === this.id) { - if(subscriber.stream) { - subscriber.destroy('streamDestroyed'); - } - } - }, this); - } else { - // @TODO Add a one time warning that this no longer cleans up the publisher - } - }, this); - - this.dispatchEvent(event, defaultAction); - }; - - archiveCreatedHandler = function(archive) { - this.dispatchEvent(new OT.ArchiveEvent('archiveStarted', archive)); - }; - - archiveDestroyedHandler = function(archive) { - this.dispatchEvent(new OT.ArchiveEvent('archiveDestroyed', archive)); - }; - - archiveUpdatedHandler = function(event) { - var archive = event.target, - propertyName = event.changedProperty, - newValue = event.newValue; - - if(propertyName === 'status' && newValue === 'stopped') { - this.dispatchEvent(new OT.ArchiveEvent('archiveStopped', archive)); - } else { - this.dispatchEvent(new OT.ArchiveEvent('archiveUpdated', archive)); - } - }; - - // Put ourselves into a pristine state - reset = function() { - this.token = _token = null; - setState('disconnected'); - this.connection = null; - this.capabilities = new OT.Capabilities([]); - this.connections.destroy(); - this.streams.destroy(); - this.archives.destroy(); - }; - - disconnectComponents = function(reason) { - OT.$.forEach(OT.publishers.where({session: this}), function(publisher) { - publisher.disconnect(reason); - }); - - OT.$.forEach(OT.subscribers.where({session: this}), function(subscriber) { - subscriber.disconnect(); - }); - }; - - destroyPublishers = function(reason) { - OT.$.forEach(OT.publishers.where({session: this}), function(publisher) { - publisher._.streamDestroyed(reason); - }); - }; - - destroySubscribers = function(reason) { - OT.$.forEach(OT.subscribers.where({session: this}), function(subscriber) { - subscriber.destroy(reason); - }); - }; - - connectMessenger = function() { - OT.debug('OT.Session: connecting to Raptor'); - - var socketUrl = this.sessionInfo.messagingURL, - symphonyUrl = OT.properties.symphonyAddresss || this.sessionInfo.symphonyAddress; - - _socket = new OT.Raptor.Socket(_widgetId, socketUrl, symphonyUrl, - OT.SessionDispatcher(this)); - - var analyticsPayload = [ - socketUrl, OT.$.userAgent(), OT.properties.version, - window.externalHost ? 'yes' : 'no' - ]; - - _socket.connect(_token, this.sessionInfo, OT.$.bind(function(error, sessionState) { - if (error) { - _socket = void 0; - analyticsPayload.splice(0,0,error.message); - this.logEvent('Connect', 'Failure', - 'reason|webSocketServerUrl|userAgent|sdkVersion|chromeFrame', - analyticsPayload.map(function(e) { return e.replace('|', '\\|'); }).join('|')); - - sessionConnectFailed.call(this, error.message, error.code); - return; - } - - OT.debug('OT.Session: Received session state from Raptor', sessionState); - - this.connection = this.connections.get(_socket.id()); - if(this.connection) { - this.capabilities = this.connection.permissions; - } - - setState('connected'); - - this.logEvent('Connect', 'Success', - 'webSocketServerUrl|userAgent|sdkVersion|chromeFrame', - OT.$.map(analyticsPayload, function(e) { - return e.replace('|', '\\|'); - }).join('|'), {connectionId: this.connection.id}); - - // Listen for our own connection's destroyed event so we know when we've been disconnected. - this.connection.on('destroyed', sessionDisconnectedHandler, this); - - // Listen for connection updates - this.connections.on({ - add: connectionCreatedHandler, - remove: connectionDestroyedHandler - }, this); - - // Listen for stream updates - this.streams.on({ - add: streamCreatedHandler, - remove: streamDestroyedHandler, - update: streamPropertyModifiedHandler - }, this); - - this.archives.on({ - add: archiveCreatedHandler, - remove: archiveDestroyedHandler, - update: archiveUpdatedHandler - }, this); - - this.dispatchEvent( - new OT.SessionConnectEvent(OT.Event.names.SESSION_CONNECTED), OT.$.bind(function() { - this.connections._triggerAddEvents(); // { id: this.connection.id } - this.streams._triggerAddEvents(); // { id: this.stream.id } - this.archives._triggerAddEvents(); - }, this) - ); - - }, this)); - }; - - getSessionInfo = function() { - if (this.is('connecting')) { - OT.SessionInfo.get( - this, - OT.$.bind(onSessionInfoResponse, this), - OT.$.bind(function(error) { - sessionConnectFailed.call(this, error.message + - (error.code ? ' (' + error.code + ')' : ''), error.code); - }, this) - ); - } - }; - - onSessionInfoResponse = function(sessionInfo) { - if (this.is('connecting')) { - var overrides = OT.properties.sessionInfoOverrides; - this.sessionInfo = sessionInfo; - if (overrides != null && typeof overrides === 'object') { - this.sessionInfo = OT.$.defaults(overrides, this.sessionInfo); - console.log('is', this.sessionInfo); - } - if (this.sessionInfo.partnerId && this.sessionInfo.partnerId !== _apiKey) { - this.apiKey = _apiKey = this.sessionInfo.partnerId; - - var reason = 'Authentication Error: The API key does not match the token or session.'; - - this.logEvent('Connect', 'Failure', 'reason', 'GetSessionInfo:' + - OT.ExceptionCodes.AUTHENTICATION_ERROR + ':' + reason); - - sessionConnectFailed.call(this, reason, OT.ExceptionCodes.AUTHENTICATION_ERROR); - } else { - connectMessenger.call(this); - } - } - }; - - // Check whether we have permissions to perform the action. - permittedTo = OT.$.bind(function(action) { - return this.capabilities.permittedTo(action); - }, this); - - dispatchError = OT.$.bind(function(code, message, completionHandler) { - OT.dispatchError(code, message, completionHandler, this); - }, this); - - this.logEvent = function(action, variation, payloadType, payload, options) { - /* jshint camelcase:false */ - var event = { - action: action, - variation: variation, - payload_type: payloadType, - payload: payload, - session_id: _sessionId, - partner_id: _apiKey, - widget_id: _widgetId, - widget_type: 'Controller' - }; - if (this.connection && this.connection.id) _connectionId = event.connection_id = - this.connection.id; - else if (_connectionId) event.connection_id = _connectionId; - - if (options) event = OT.$.extend(options, event); - _analytics.logEvent(event); - }; - - /** - * Connects to an OpenTok session. - *

- * Upon a successful connection, the completion handler (the second parameter of the method) is - * invoked without an error object passed in. (If there is an error connecting, the completion - * handler is invoked with an error object.) Make sure that you have successfully connected to the - * session before calling other methods of the Session object. - *

- *

- * The Session object dispatches a connectionCreated event when any client - * (including your own) connects to to the session. - *

- * - *
- * Example - *
- *

- * The following code initializes a session and sets up an event listener for when the session - * connects: - *

- *
- *  var apiKey = ""; // Replace with your API key. See https://dashboard.tokbox.com/projects
- *  var sessionID = ""; // Replace with your own session ID.
- *                      // See https://dashboard.tokbox.com/projects
- *  var token = ""; // Replace with a generated token that has been assigned the moderator role.
- *                  // See https://dashboard.tokbox.com/projects
- *
- *  var session = OT.initSession(apiKey, sessionID);
- *  session.on("sessionConnected", function(sessionConnectEvent) {
- *      //
- *  });
- *  session.connect(token);
- *  
- *

- *

- * In this example, the sessionConnectHandler() function is passed an event - * object of type {@link SessionConnectEvent}. - *

- * - *
- * Events dispatched: - *
- * - *

- * exception (ExceptionEvent) — Dispatched - * by the OT class locally in the event of an error. - *

- *

- * connectionCreated (ConnectionEvent) — - * Dispatched by the Session object on all clients connected to the session. - *

- *

- * sessionConnected (SessionConnectEvent) - * — Dispatched locally by the Session object when the connection is established. - *

- * - * @param {String} token The session token. You generate a session token using our - * server-side libraries or the - * Dashboard page. For more information, see - * Connection token creation. - * - * @param {Function} completionHandler (Optional) A function to be called when the call to the - * connect() method succeeds or fails. This function takes one parameter — - * error (see the Error object). - * On success, the completionHandler function is not passed any - * arguments. On error, the function is passed an error object parameter - * (see the Error object). The - * error object has two properties: code (an integer) and - * message (a string), which identify the cause of the failure. The following - * code adds a completionHandler when calling the connect() method: - *
-  * session.connect(token, function (error) {
-  *   if (error) {
-  *       console.log(error.message);
-  *   } else {
-  *     console.log("Connected to session.");
-  *   }
-  * });
-  * 
- *

- * Note that upon connecting to the session, the Session object dispatches a - * sessionConnected event in addition to calling the completionHandler. - * The SessionConnectEvent object, which defines the sessionConnected event, - * includes connections and streams properties, which - * list the connections and streams in the session when you connect. - *

- * - * @see SessionConnectEvent - * @method #connect - * @memberOf Session - */ - this.connect = function(token) { - - if(apiKey == null && arguments.length > 1 && - (typeof arguments[0] === 'string' || typeof arguments[0] === 'number') && - typeof arguments[1] === 'string') { - _apiKey = token.toString(); - token = arguments[1]; - } - - // The completion handler is always the last argument. - var completionHandler = arguments[arguments.length - 1]; - - if (this.is('connecting', 'connected')) { - OT.warn('OT.Session: Cannot connect, the session is already ' + this.state); - return this; - } - - reset.call(this); - setState('connecting'); - this.token = _token = !OT.$.isFunction(token) && token; - - // Get a new widget ID when reconnecting. - if (_initialConnection) { - _initialConnection = false; - } else { - _widgetId = OT.$.uuid(); - } - - if (completionHandler && OT.$.isFunction(completionHandler)) { - this.once('sessionConnected', OT.$.bind(completionHandler, null, null)); - this.once('sessionConnectFailed', completionHandler); - } - - if(_apiKey == null || OT.$.isFunction(_apiKey)) { - setTimeout(OT.$.bind( - sessionConnectFailed, - this, - 'API Key is undefined. You must pass an API Key to initSession.', - OT.ExceptionCodes.AUTHENTICATION_ERROR - )); - - return this; - } - - if (!_sessionId) { - setTimeout(OT.$.bind( - sessionConnectFailed, - this, - 'SessionID is undefined. You must pass a sessionID to initSession.', - OT.ExceptionCodes.INVALID_SESSION_ID - )); - - return this; - } - - this.apiKey = _apiKey = _apiKey.toString(); - - // Ugly hack, make sure OT.APIKEY is set - if (OT.APIKEY.length === 0) { - OT.APIKEY = _apiKey; - } - - var analyticsPayload = [ - OT.$.userAgent(), OT.properties.version, - window.externalHost ? 'yes' : 'no' - ]; - this.logEvent( 'Connect', 'Attempt', - 'userAgent|sdkVersion|chromeFrame', - analyticsPayload.map(function(e) { return e.replace('|', '\\|'); }).join('|') - ); - - getSessionInfo.call(this); - return this; - }; - - /** - * Disconnects from the OpenTok session. - * - *

- * Calling the disconnect() method ends your connection with the session. In the - * course of terminating your connection, it also ceases publishing any stream(s) you were - * publishing. - *

- *

- * Session objects on remote clients dispatch streamDestroyed events for any - * stream you were publishing. The Session object dispatches a sessionDisconnected - * event locally. The Session objects on remote clients dispatch connectionDestroyed - * events, letting other connections know you have left the session. The - * {@link SessionDisconnectEvent} and {@link StreamEvent} objects that define the - * sessionDisconnect and connectionDestroyed events each have a - * reason property. The reason property lets the developer determine - * whether the connection is being terminated voluntarily and whether any streams are being - * destroyed as a byproduct of the underlying connection's voluntary destruction. - *

- *

- * If the session is not currently connected, calling this method causes a warning to be logged. - * See OT.setLogLevel(). - *

- * - *

- * Note: If you intend to reuse a Publisher object created using - * OT.initPublisher() to publish to different sessions sequentially, call either - * Session.disconnect() or Session.unpublish(). Do not call both. - * Then call the preventDefault() method of the streamDestroyed or - * sessionDisconnected event object to prevent the Publisher object from being - * removed from the page. Be sure to call preventDefault() only if the - * connection.connectionId property of the Stream object in the event matches the - * connection.connectionId property of your Session object (to ensure that you are - * preventing the default behavior for your published streams, not for other streams that you - * subscribe to). - *

- * - *
- * Events dispatched: - *
- *

- * sessionDisconnected - * (SessionDisconnectEvent) - * — Dispatched locally when the connection is disconnected. - *

- *

- * connectionDestroyed (ConnectionEvent) — - * Dispatched on other clients, along with the streamDestroyed event (as warranted). - *

- * - *

- * streamDestroyed (StreamEvent) — - * Dispatched on other clients if streams are lost as a result of the session disconnecting. - *

- * - * @method #disconnect - * @memberOf Session - */ - var disconnect = OT.$.bind(function disconnect(drainSocketBuffer) { - if (_socket && _socket.isNot('disconnected')) { - if (_socket.isNot('disconnecting')) { - setState('disconnecting'); - _socket.disconnect(drainSocketBuffer); - } - } - else { - reset.call(this); - } - }, this); - - this.disconnect = function(drainSocketBuffer) { - disconnect(drainSocketBuffer !== void 0 ? drainSocketBuffer : true); - }; - - this.destroy = function(reason) { - this.streams.destroy(); - this.connections.destroy(); - this.archives.destroy(); - disconnect(reason !== 'unloaded'); - }; - - /** - * The publish() method starts publishing an audio-video stream to the session. - * The audio-video stream is captured from a local microphone and webcam. Upon successful - * publishing, the Session objects on all connected clients dispatch the - * streamCreated event. - *

- * - * - *

You pass a Publisher object as the one parameter of the method. You can initialize a - * Publisher object by calling the OT.initPublisher() - * method. Before calling Session.publish(). - *

- * - *

This method takes an alternate form: publish([targetElement:String, - * properties:Object]):Publisher — In this form, you do not pass a Publisher - * object into the function. Instead, you pass in a targetElement (the ID of the - * DOM element that the Publisher will replace) and a properties object that - * defines options for the Publisher (see OT.initPublisher().) - * The method returns a new Publisher object, which starts sending an audio-video stream to the - * session. The remainder of this documentation describes the form that takes a single Publisher - * object as a parameter. - * - *

- * A local display of the published stream is created on the web page by replacing - * the specified element in the DOM with a streaming video display. The video stream - * is automatically mirrored horizontally so that users see themselves and movement - * in their stream in a natural way. If the width and height of the display do not match - * the 4:3 aspect ratio of the video signal, the video stream is cropped to fit the - * display. - *

- * - *

- * If calling this method creates a new Publisher object and the OpenTok library does not - * have access to the camera or microphone, the web page alerts the user to grant access - * to the camera and microphone. - *

- * - *

- * The OT object dispatches an exception event if the user's role does not - * include permissions required to publish. For example, if the user's role is set to subscriber, - * then they cannot publish. You define a user's role when you create the user token using the - * generate_token() method of the - * OpenTok server-side - * libraries or the Dashboard page. - * You pass the token string as a parameter of the connect() method of the Session - * object. See ExceptionEvent and - * OT.on(). - *

- *

- * The application throws an error if the session is not connected. - *

- * - *
Events dispatched:
- *

- * exception (ExceptionEvent) — Dispatched - * by the OT object. This can occur when user's role does not allow publishing (the - * code property of event object is set to 1500); it can also occur if the c - * onnection fails to connect (the code property of event object is set to 1013). - * WebRTC is a peer-to-peer protocol, and it is possible that connections will fail to connect. - * The most common cause for failure is a firewall that the protocol cannot traverse. - *

- *

- * streamCreated (StreamEvent) — - * The stream has been published. The Session object dispatches this on all clients - * subscribed to the stream, as well as on the publisher's client. - *

- * - *
Example
- * - *

- * The following example publishes a video once the session connects: - *

- *
-  * var sessionId = ""; // Replace with your own session ID.
-  *                     // See https://dashboard.tokbox.com/projects
-  * var token = ""; // Replace with a generated token that has been assigned the moderator role.
-  *                 // See https://dashboard.tokbox.com/projects
-  * var session = OT.initSession(apiKey, sessionID);
-  * session.on("sessionConnected", function (event) {
-  *     var publisherOptions = {width: 400, height:300, name:"Bob's stream"};
-  *     // This assumes that there is a DOM element with the ID 'publisher':
-  *     publisher = OT.initPublisher('publisher', publisherOptions);
-  *     session.publish(publisher);
-  * });
-  * session.connect(token);
-  * 
- * - * @param {Publisher} publisher A Publisher object, which you initialize by calling the - * OT.initPublisher() method. - * - * @param {Function} completionHandler (Optional) A function to be called when the call to the - * publish() method succeeds or fails. This function takes one parameter — - * error. On success, the completionHandler function is not passed any - * arguments. On error, the function is passed an error object parameter - * (see the Error object). The - * error object has two properties: code (an integer) and - * message (a string), which identify the cause of the failure. Calling - * publish() fails if the role assigned to your token is not "publisher" or - * "moderator"; in this case error.code is set to 1500. Calling - * publish() also fails the client fails to connect; in this case - * error.code is set to 1013. The following code adds a - * completionHandler when calling the publish() method: - *
-  * session.publish(publisher, null, function (error) {
-  *   if (error) {
-  *     console.log(error.message);
-  *   } else {
-  *     console.log("Publishing a stream.");
-  *   }
-  * });
-  * 
- * - * @returns The Publisher object for this stream. - * - * @method #publish - * @memberOf Session - */ - this.publish = function(publisher, properties, completionHandler) { - if(typeof publisher === 'function') { - completionHandler = publisher; - publisher = undefined; - } - if(typeof properties === 'function') { - completionHandler = properties; - properties = undefined; - } - if (this.isNot('connected')) { - /*jshint camelcase:false*/ - _analytics.logError(1010, 'OT.exception', - 'We need to be connected before you can publish', null, { - action: 'publish', - variation: 'Failure', - payload_type: 'reason', - payload: 'We need to be connected before you can publish', - session_id: _sessionId, - partner_id: _apiKey, - widgetId: _widgetId, - widget_type: 'Controller' - }); - - if (completionHandler && OT.$.isFunction(completionHandler)) { - dispatchError(OT.ExceptionCodes.NOT_CONNECTED, - 'We need to be connected before you can publish', completionHandler); - } - - return null; - } - - if (!permittedTo('publish')) { - this.logEvent('publish', 'Failure', 'reason', - 'This token does not allow publishing. The role must be at least `publisher` ' + - 'to enable this functionality'); - dispatchError(OT.ExceptionCodes.UNABLE_TO_PUBLISH, - 'This token does not allow publishing. The role must be at least `publisher` ' + - 'to enable this functionality', completionHandler); - return null; - } - - // If the user has passed in an ID of a element then we create a new publisher. - if (!publisher || typeof(publisher)==='string' || OT.$.isElementNode(publisher)) { - // Initiate a new Publisher with the new session credentials - publisher = OT.initPublisher(publisher, properties); - - } else if (publisher instanceof OT.Publisher){ - - // If the publisher already has a session attached to it we can - if ('session' in publisher && publisher.session && 'sessionId' in publisher.session) { - // send a warning message that we can't publish again. - if( publisher.session.sessionId === this.sessionId){ - OT.warn('Cannot publish ' + publisher.guid() + ' again to ' + - this.sessionId + '. Please call session.unpublish(publisher) first.'); - } else { - OT.warn('Cannot publish ' + publisher.guid() + ' publisher already attached to ' + - publisher.session.sessionId+ '. Please call session.unpublish(publisher) first.'); - } - } - - } else { - dispatchError(OT.ExceptionCodes.UNABLE_TO_PUBLISH, - 'Session.publish :: First parameter passed in is neither a ' + - 'string nor an instance of the Publisher', - completionHandler); - return; - } - - publisher.once('publishComplete', function(err) { - if (err) { - dispatchError(OT.ExceptionCodes.UNABLE_TO_PUBLISH, - 'Session.publish :: ' + err.message, - completionHandler); - return; - } - - if (completionHandler && OT.$.isFunction(completionHandler)) { - completionHandler.apply(null, arguments); - } - }); - - // Add publisher reference to the session - publisher._.publishToSession(this); - - // return the embed publisher - return publisher; - }; - - /** - * Ceases publishing the specified publisher's audio-video stream - * to the session. By default, the local representation of the audio-video stream is - * removed from the web page. Upon successful termination, the Session object on every - * connected web page dispatches - * a streamDestroyed event. - *

- * - *

- * To prevent the Publisher from being removed from the DOM, add an event listener for the - * streamDestroyed event dispatched by the Publisher object and call the - * preventDefault() method of the event object. - *

- * - *

- * Note: If you intend to reuse a Publisher object created using - * OT.initPublisher() to publish to different sessions sequentially, call - * either Session.disconnect() or Session.unpublish(). Do not call - * both. Then call the preventDefault() method of the streamDestroyed - * or sessionDisconnected event object to prevent the Publisher object from being - * removed from the page. Be sure to call preventDefault() only if the - * connection.connectionId property of the Stream object in the event matches the - * connection.connectionId property of your Session object (to ensure that you are - * preventing the default behavior for your published streams, not for other streams that you - * subscribe to). - *

- * - *
Events dispatched:
- * - *

- * streamDestroyed (StreamEvent) — - * The stream associated with the Publisher has been destroyed. Dispatched on by the - * Publisher on on the Publisher's browser. Dispatched by the Session object on - * all other connections subscribing to the publisher's stream. - *

- * - *
Example
- * - * The following example publishes a stream to a session and adds a Disconnect link to the - * web page. Clicking this link causes the stream to stop being published. - * - *
-  * <script>
-  *     var apiKey = ""; // Replace with your API key. See https://dashboard.tokbox.com/projects
-  *     var sessionID = ""; // Replace with your own session ID.
-  *                      // See https://dashboard.tokbox.com/projects
-  *     var token = "Replace with the TokBox token string provided to you."
-  *     var session = OT.initSession(apiKey, sessionID);
-  *     session.on("sessionConnected", function sessionConnectHandler(event) {
-  *         // This assumes that there is a DOM element with the ID 'publisher':
-  *         publisher = OT.initPublisher('publisher');
-  *         session.publish(publisher);
-  *     });
-  *     session.connect(token);
-  *     var publisher;
-  *
-  *     function unpublish() {
-  *         session.unpublish(publisher);
-  *     }
-  * </script>
-  *
-  * <body>
-  *
-  *     <div id="publisherContainer/>
-  *     <br/>
-  *
-  *     <a href="javascript:unpublish()">Stop Publishing</a>
-  *
-  * </body>
-  *
-  * 
- * - * @see publish() - * - * @see streamDestroyed event - * - * @param {Publisher} publisher The Publisher object to stop streaming. - * - * @method #unpublish - * @memberOf Session - */ - this.unpublish = function(publisher) { - if (!publisher) { - OT.error('OT.Session.unpublish: publisher parameter missing.'); - return; - } - - // Unpublish the localMedia publisher - publisher._.unpublishFromSession(this, 'unpublished'); - }; - - - /** - * Subscribes to a stream that is available to the session. You can get an array of - * available streams from the streams property of the sessionConnected - * and streamCreated events (see - * SessionConnectEvent and - * StreamEvent). - *

- *

- * The subscribed stream is displayed on the local web page by replacing the specified element - * in the DOM with a streaming video display. If the width and height of the display do not - * match the 4:3 aspect ratio of the video signal, the video stream is cropped to fit - * the display. If the stream lacks a video component, a blank screen with an audio indicator - * is displayed in place of the video stream. - *

- * - *

- * The application throws an error if the session is not connected or if the - * targetElement does not exist in the HTML DOM. - *

- * - *
Example
- * - * The following code subscribes to other clients' streams: - * - *
-  * var apiKey = ""; // Replace with your API key. See https://dashboard.tokbox.com/projects
-  * var sessionID = ""; // Replace with your own session ID.
-  *                     // See https://dashboard.tokbox.com/projects
-  *
-  * var session = OT.initSession(apiKey, sessionID);
-  * session.on("streamCreated", function(event) {
-  *   subscriber = session.subscribe(event.stream, targetElement);
-  * });
-  * session.connect(token);
-  * 
- * - * @param {Stream} stream The Stream object representing the stream to which we are trying to - * subscribe. - * - * @param {Object} targetElement (Optional) The DOM element or the id attribute of - * the existing DOM element used to determine the location of the Subscriber video in the HTML - * DOM. See the insertMode property of the properties parameter. If - * you do not specify a targetElement, the application appends a new DOM element - * to the HTML body. - * - * @param {Object} properties This is an object that contains the following properties: - *
    - *
  • audioVolume (Number) — The desired audio volume, between 0 and - * 100, when the Subscriber is first opened (default: 50). After you subscribe to the - * stream, you can adjust the volume by calling the - * setAudioVolume() method of the - * Subscriber object. This volume setting affects local playback only; it does not affect - * the stream's volume on other clients.
  • - * - *
  • height (Number) — The desired height, in pixels, of the - * displayed Subscriber video stream (default: 198). Note: Use the - * height and width properties to set the dimensions - * of the Subscriber video; do not set the height and width of the DOM element - * (using CSS).
  • - * - *
  • - * insertMode (String) — Specifies how the Subscriber object will - * be inserted in the HTML DOM. See the targetElement parameter. This - * string can have the following values: - *
      - *
    • "replace" — The Subscriber object replaces contents of the - * targetElement. This is the default.
    • - *
    • "after" — The Subscriber object is a new element inserted - * after the targetElement in the HTML DOM. (Both the Subscriber and targetElement - * have the same parent element.)
    • - *
    • "before" — The Subscriber object is a new element inserted - * before the targetElement in the HTML DOM. (Both the Subsciber and targetElement - * have the same parent element.)
    • - *
    • "append" — The Subscriber object is a new element added as a - * child of the targetElement. If there are other child elements, the Subscriber is - * appended as the last child element of the targetElement.
    • - *
    - *
  • - * - *
  • - * style (Object) — An object containing properties that define the initial - * appearance of user interface controls of the Subscriber. The style object - * includes the following properties: - *
      - *
    • audioLevelDisplayMode (String) — How to display the audio level - * indicator. Possible values are: "auto" (the indicator is displayed when the - * video is disabled), "off" (the indicator is not displayed), and - * "on" (the indicator is always displayed).
    • - * - *
    • backgroundImageURI (String) — A URI for an image to display as - * the background image when a video is not displayed. (A video may not be displayed if - * you call subscribeToVideo(false) on the Subscriber object). You can pass an - * http or https URI to a PNG, JPEG, or non-animated GIF file location. You can also use the - * data URI scheme (instead of http or https) and pass in base-64-encrypted - * PNG data, such as that obtained from the - * Subscriber.getImgData() method. For example, - * you could set the property to "data:VBORw0KGgoAA...", where the portion of - * the string after "data:" is the result of a call to - * Subscriber.getImgData(). If the URL or the image data is invalid, the - * property is ignored (the attempt to set the image fails silently). - *

      - * Note that in Internet Explorer 8 (using the OpenTok Plugin for Internet Explorer), - * you cannot set the backgroundImageURI style to a string larger than - * 32 kB. This is due to an IE 8 limitation on the size of URI strings. Due to this - * limitation, you cannot set the backgroundImageURI style to a string obtained - * with the getImgData() method. - *

    • - * - *
    • buttonDisplayMode (String) — How to display the speaker controls - * Possible values are: "auto" (controls are displayed when the stream is first - * displayed and when the user mouses over the display), "off" (controls are not - * displayed), and "on" (controls are always displayed).
    • - * - *
    • nameDisplayMode (String) — Whether to display the stream name. - * Possible values are: "auto" (the name is displayed when the stream is first - * displayed and when the user mouses over the display), "off" (the name is not - * displayed), and "on" (the name is always displayed).
    • - * - *
    • videoDisabledDisplayMode (String) — Whether to display the video - * disabled indicator and video disabled warning icons for a Subscriber. These icons - * indicate that the video has been disabled (or is in risk of being disabled for - * the warning icon) due to poor stream quality. This style only applies to the Subscriber - * object. Possible values are: "auto" (the icons are automatically when the - * displayed video is disabled or in risk of being disabled due to poor stream quality), - * "off" (do not display the icons), and "on" (display the - * icons). The default setting is "auto"
    • - *
    - *
  • - * - *
  • subscribeToAudio (Boolean) — Whether to initially subscribe to audio - * (if available) for the stream (default: true).
  • - * - *
  • subscribeToVideo (Boolean) — Whether to initially subscribe to video - * (if available) for the stream (default: true).
  • - * - *
  • width (Number) — The desired width, in pixels, of the - * displayed Subscriber video stream (default: 264). Note: Use the - * height and width properties to set the dimensions - * of the Subscriber video; do not set the height and width of the DOM element - * (using CSS).
  • - * - *
- * - * @param {Function} completionHandler (Optional) A function to be called when the call to the - * subscribe() method succeeds or fails. This function takes one parameter — - * error. On success, the completionHandler function is not passed any - * arguments. On error, the function is passed an error object, defined by the - * Error class, has two properties: code (an integer) and - * message (a string), which identify the cause of the failure. The following - * code adds a completionHandler when calling the subscribe() method: - *
-  * session.subscribe(stream, "subscriber", null, function (error) {
-  *   if (error) {
-  *     console.log(error.message);
-  *   } else {
-  *     console.log("Subscribed to stream: " + stream.id);
-  *   }
-  * });
-  * 
- * - * @signature subscribe(stream, targetElement, properties, completionHandler) - * @returns {Subscriber} The Subscriber object for this stream. Stream control functions - * are exposed through the Subscriber object. - * @method #subscribe - * @memberOf Session - */ - this.subscribe = function(stream, targetElement, properties, completionHandler) { - - if (!this.connection || !this.connection.connectionId) { - dispatchError(OT.ExceptionCodes.UNABLE_TO_SUBSCRIBE, - 'Session.subscribe :: Connection required to subscribe', - completionHandler); - return; - } - - if (!stream) { - dispatchError(OT.ExceptionCodes.UNABLE_TO_SUBSCRIBE, - 'Session.subscribe :: stream cannot be null', - completionHandler); - return; - } - - if (!stream.hasOwnProperty('streamId')) { - dispatchError(OT.ExceptionCodes.UNABLE_TO_SUBSCRIBE, - 'Session.subscribe :: invalid stream object', - completionHandler); - return; - } - - if(typeof targetElement === 'function') { - completionHandler = targetElement; - targetElement = undefined; - } - - if(typeof properties === 'function') { - completionHandler = properties; - properties = undefined; - } - - var subscriber = new OT.Subscriber(targetElement, OT.$.extend(properties || {}, { - session: this - })); - - subscriber.once('subscribeComplete', function(err) { - if (err) { - dispatchError(OT.ExceptionCodes.UNABLE_TO_SUBSCRIBE, - 'Session.subscribe :: ' + err.message, - completionHandler); - - return; - } - - if (completionHandler && OT.$.isFunction(completionHandler)) { - completionHandler.apply(null, arguments); - } - }); - - OT.subscribers.add(subscriber); - subscriber.subscribe(stream); - - return subscriber; - }; - - /** - * Stops subscribing to a stream in the session. the display of the audio-video stream is - * removed from the local web page. - * - *
Example
- *

- * The following code subscribes to other clients' streams. For each stream, the code also - * adds an Unsubscribe link. - *

- *
-  * var apiKey = ""; // Replace with your API key. See https://dashboard.tokbox.com/projects
-  * var sessionID = ""; // Replace with your own session ID.
-  *                     // See https://dashboard.tokbox.com/projects
-  * var streams = [];
-  *
-  * var session = OT.initSession(apiKey, sessionID);
-  * session.on("streamCreated", function(event) {
-  *     var stream = event.stream;
-  *     displayStream(stream);
-  * });
-  * session.connect(token);
-  *
-  * function displayStream(stream) {
-  *     var div = document.createElement('div');
-  *     div.setAttribute('id', 'stream' + stream.streamId);
-  *
-  *     var subscriber = session.subscribe(stream, div);
-  *     subscribers.push(subscriber);
-  *
-  *     var aLink = document.createElement('a');
-  *     aLink.setAttribute('href', 'javascript: unsubscribe("' + subscriber.id + '")');
-  *     aLink.innerHTML = "Unsubscribe";
-  *
-  *     var streamsContainer = document.getElementById('streamsContainer');
-  *     streamsContainer.appendChild(div);
-  *     streamsContainer.appendChild(aLink);
-  *
-  *     streams = event.streams;
-  * }
-  *
-  * function unsubscribe(subscriberId) {
-  *     console.log("unsubscribe called");
-  *     for (var i = 0; i < subscribers.length; i++) {
-  *         var subscriber = subscribers[i];
-  *         if (subscriber.id == subscriberId) {
-  *             session.unsubscribe(subscriber);
-  *         }
-  *     }
-  * }
-  * 
- * - * @param {Subscriber} subscriber The Subscriber object to unsubcribe. - * - * @see subscribe() - * - * @method #unsubscribe - * @memberOf Session - */ - this.unsubscribe = function(subscriber) { - if (!subscriber) { - var errorMsg = 'OT.Session.unsubscribe: subscriber cannot be null'; - OT.error(errorMsg); - throw new Error(errorMsg); - } - - if (!subscriber.stream) { - OT.warn('OT.Session.unsubscribe:: tried to unsubscribe a subscriber that had no stream'); - return false; - } - - OT.debug('OT.Session.unsubscribe: subscriber ' + subscriber.id); - - subscriber.destroy(); - - return true; - }; - - /** - * Returns an array of local Subscriber objects for a given stream. - * - * @param {Stream} stream The stream for which you want to find subscribers. - * - * @returns {Array} An array of {@link Subscriber} objects for the specified stream. - * - * @see unsubscribe() - * @see Subscriber - * @see StreamEvent - * @method #getSubscribersForStream - * @memberOf Session - */ - this.getSubscribersForStream = function(stream) { - return OT.subscribers.where({streamId: stream.id}); - }; - - /** - * Returns the local Publisher object for a given stream. - * - * @param {Stream} stream The stream for which you want to find the Publisher. - * - * @returns {Publisher} A Publisher object for the specified stream. Returns - * null if there is no local Publisher object - * for the specified stream. - * - * @see forceUnpublish() - * @see Subscriber - * @see StreamEvent - * - * @method #getPublisherForStream - * @memberOf Session - */ - this.getPublisherForStream = function(stream) { - var streamId, - errorMsg; - - if (typeof stream === 'string') { - streamId = stream; - } else if (typeof stream === 'object' && stream && stream.hasOwnProperty('id')) { - streamId = stream.id; - } else { - errorMsg = 'Session.getPublisherForStream :: Invalid stream type'; - OT.error(errorMsg); - throw new Error(errorMsg); - } - - return OT.publishers.where({streamId: streamId})[0]; - }; - - // Private Session API: for internal OT use only - this._ = { - jsepCandidateP2p: function(streamId, subscriberId, candidate) { - return _socket.jsepCandidateP2p(streamId, subscriberId, candidate); - }, - - jsepCandidate: function(streamId, candidate) { - return _socket.jsepCandidate(streamId, candidate); - }, - - jsepOffer: function(streamId, offerSdp) { - return _socket.jsepOffer(streamId, offerSdp); - }, - - jsepOfferP2p: function(streamId, subscriberId, offerSdp) { - return _socket.jsepOfferP2p(streamId, subscriberId, offerSdp); - }, - - jsepAnswer: function(streamId, answerSdp) { - return _socket.jsepAnswer(streamId, answerSdp); - }, - - jsepAnswerP2p: function(streamId, subscriberId, answerSdp) { - return _socket.jsepAnswerP2p(streamId, subscriberId, answerSdp); - }, - - // session.on("signal", function(SignalEvent)) - // session.on("signal:{type}", function(SignalEvent)) - dispatchSignal: OT.$.bind(function(fromConnection, type, data) { - var event = new OT.SignalEvent(type, data, fromConnection); - event.target = this; - - // signal a "signal" event - // NOTE: trigger doesn't support defaultAction, and therefore preventDefault. - this.trigger(OT.Event.names.SIGNAL, event); - - // signal an "signal:{type}" event" if there was a custom type - if (type) this.dispatchEvent(event); - }, this), - - subscriberCreate: function(stream, subscriber, channelsToSubscribeTo, completion) { - return _socket.subscriberCreate(stream.id, subscriber.widgetId, - channelsToSubscribeTo, completion); - }, - - subscriberDestroy: function(stream, subscriber) { - return _socket.subscriberDestroy(stream.id, subscriber.widgetId); - }, - - subscriberUpdate: function(stream, subscriber, attributes) { - return _socket.subscriberUpdate(stream.id, subscriber.widgetId, attributes); - }, - - subscriberChannelUpdate: function(stream, subscriber, channel, attributes) { - return _socket.subscriberChannelUpdate(stream.id, subscriber.widgetId, channel.id, - attributes); - }, - - streamCreate: OT.$.bind(function(name, orientation, encodedWidth, encodedHeight, - hasAudio, hasVideo, - frameRate, completion) { - - _socket.streamCreate( - name, - orientation, - encodedWidth, - encodedHeight, - hasAudio, - hasVideo, - frameRate, - OT.Config.get('bitrates', 'min', OT.APIKEY), - OT.Config.get('bitrates', 'max', OT.APIKEY), - completion - ); - }, this), - - streamDestroy: function(streamId) { - _socket.streamDestroy(streamId); - }, - - streamChannelUpdate: function(stream, channel, attributes) { - _socket.streamChannelUpdate(stream.id, channel.id, attributes); - } - }; - - - /** - * Sends a signal to each client or a specified client in the session. Specify a - * to property of the signal parameter to limit the signal to - * be sent to a specific client; otherwise the signal is sent to each client connected to - * the session. - *

- * The following example sends a signal of type "foo" with a specified data payload ("hello") - * to all clients connected to the session: - *

-  * session.signal({
-  *     type: "foo",
-  *     data: "hello"
-  *   },
-  *   function(error) {
-  *     if (error) {
-  *       console.log("signal error: " + error.message);
-  *     } else {
-  *       console.log("signal sent");
-  *     }
-  *   }
-  * );
-  * 
- *

- * Calling this method without specifying a recipient client (by setting the to - * property of the signal parameter) results in multiple signals sent (one to each - * client in the session). For information on charges for signaling, see the - * OpenTok pricing page. - *

- * The following example sends a signal of type "foo" with a data payload ("hello") to a - * specific client connected to the session: - *

-  * session.signal({
-  *     type: "foo",
-  *     to: recipientConnection; // a Connection object
-  *     data: "hello"
-  *   },
-  *   function(error) {
-  *     if (error) {
-  *       console.log("signal error: " + error.message);
-  *     } else {
-  *       console.log("signal sent");
-  *     }
-  *   }
-  * );
-  * 
- *

- * Add an event handler for the signal event to listen for all signals sent in - * the session. Add an event handler for the signal:type event to listen for - * signals of a specified type only (replace type, in signal:type, - * with the type of signal to listen for). The Session object dispatches these events. (See - * events.) - * - * @param {Object} signal An object that contains the following properties defining the signal: - *

    - *
  • data — (String) The data to send. The limit to the length of data - * string is 8kB. Do not set the data string to null or - * undefined.
  • - *
  • to — (Connection) A Connection - * object corresponding to the client that the message is to be sent to. If you do not - * specify this property, the signal is sent to all clients connected to the session.
  • - *
  • type — (String) The type of the signal. You can use the type to - * filter signals when setting an event handler for the signal:type event - * (where you replace type with the type string). The maximum length of the - * type string is 128 characters, and it must contain only letters (A-Z and a-z), - * numbers (0-9), '-', '_', and '~'.
  • - * - *
- * - *

Each property is optional. If you set none of the properties, you will send a signal - * with no data or type to each client connected to the session.

- * - * @param {Function} completionHandler A function that is called when sending the signal - * succeeds or fails. This function takes one parameter — error. - * On success, the completionHandler function is not passed any - * arguments. On error, the function is passed an error object, defined by the - * Error class. The error object has the following - * properties: - * - *
    - *
  • code — (Number) An error code, which can be one of the following: - * - * - * - * - * - * - * - * - * - * - * - * - * - *
    400 One of the signal properties — data, type, or to — - * is invalid.
    404 The client specified by the to property is not connected to - * the session.
    413 The type string exceeds the maximum length (128 bytes), - * or the data string exceeds the maximum size (8 kB).
    500 You are not connected to the OpenTok session.
    - *
  • - *
  • message — (String) A description of the error.
  • - *
- * - *

Note that the completionHandler success result (error == null) - * indicates that the options passed into the Session.signal() method are valid - * and the signal was sent. It does not indicate that the signal was successfully - * received by any of the intended recipients. - * - * @method #signal - * @memberOf Session - * @see signal and signal:type events - */ - this.signal = function(options, completion) { - var _options = options, - _completion = completion; - - if (OT.$.isFunction(_options)) { - _completion = _options; - _options = null; - } - - if (this.isNot('connected')) { - var notConnectedErrorMsg = 'Unable to send signal - you are not connected to the session.'; - dispatchError(500, notConnectedErrorMsg, _completion); - return; - } - - _socket.signal(_options, _completion); - if (options && options.data && (typeof(options.data) !== 'string')) { - OT.warn('Signaling of anything other than Strings is deprecated. ' + - 'Please update the data property to be a string.'); - } - this.logEvent('signal', 'send', 'type', - (options && options.data) ? typeof(options.data) : 'null'); - }; - - /** - * Forces a remote connection to leave the session. - * - *

- * The forceDisconnect() method is normally used as a moderation tool - * to remove users from an ongoing session. - *

- *

- * When a connection is terminated using the forceDisconnect(), - * sessionDisconnected, connectionDestroyed and - * streamDestroyed events are dispatched in the same way as they - * would be if the connection had terminated itself using the disconnect() - * method. However, the reason property of a {@link ConnectionEvent} or - * {@link StreamEvent} object specifies "forceDisconnected" as the reason - * for the destruction of the connection and stream(s). - *

- *

- * While you can use the forceDisconnect() method to terminate your own connection, - * calling the disconnect() method is simpler. - *

- *

- * The OT object dispatches an exception event if the user's role - * does not include permissions required to force other users to disconnect. - * You define a user's role when you create the user token using the - * generate_token() method using - * OpenTok - * server-side libraries or the - * Dashboard page. - * See ExceptionEvent and OT.on(). - *

- *

- * The application throws an error if the session is not connected. - *

- * - *
Events dispatched:
- * - *

- * connectionDestroyed (ConnectionEvent) — - * On clients other than which had the connection terminated. - *

- *

- * exception (ExceptionEvent) — - * The user's role does not allow forcing other user's to disconnect (event.code = - * 1530), - * or the specified stream is not publishing to the session (event.code = 1535). - *

- *

- * sessionDisconnected - * (SessionDisconnectEvent) — - * On the client which has the connection terminated. - *

- *

- * streamDestroyed (StreamEvent) — - * If streams are stopped as a result of the connection ending. - *

- * - * @param {Connection} connection The connection to be disconnected from the session. - * This value can either be a Connection object or a connection - * ID (which can be obtained from the connectionId property of the Connection object). - * - * @param {Function} completionHandler (Optional) A function to be called when the call to the - * forceDiscononnect() method succeeds or fails. This function takes one parameter - * — error. On success, the completionHandler function is - * not passed any arguments. On error, the function is passed an error object - * parameter. The error object, defined by the Error - * class, has two properties: code (an integer) - * and message (a string), which identify the cause of the failure. - * Calling forceDisconnect() fails if the role assigned to your - * token is not "moderator"; in this case error.code is set to 1520. The following - * code adds a completionHandler when calling the forceDisconnect() - * method: - *
-  * session.forceDisconnect(connection, function (error) {
-  *   if (error) {
-  *       console.log(error);
-  *     } else {
-  *       console.log("Connection forced to disconnect: " + connection.id);
-  *     }
-  *   });
-  * 
- * - * @method #forceDisconnect - * @memberOf Session - */ - - this.forceDisconnect = function(connectionOrConnectionId, completionHandler) { - if (this.isNot('connected')) { - var notConnectedErrorMsg = 'Cannot call forceDisconnect(). You are not ' + - 'connected to the session.'; - dispatchError(OT.ExceptionCodes.NOT_CONNECTED, notConnectedErrorMsg, completionHandler); - return; - } - - var notPermittedErrorMsg = 'This token does not allow forceDisconnect. ' + - 'The role must be at least `moderator` to enable this functionality'; - - if (permittedTo('forceDisconnect')) { - var connectionId = typeof connectionOrConnectionId === 'string' ? - connectionOrConnectionId : connectionOrConnectionId.id; - - _socket.forceDisconnect(connectionId, function(err) { - if (err) { - dispatchError(OT.ExceptionCodes.UNABLE_TO_FORCE_DISCONNECT, - notPermittedErrorMsg, completionHandler); - - } else if (completionHandler && OT.$.isFunction(completionHandler)) { - completionHandler.apply(null, arguments); - } - }); - } else { - // if this throws an error the handleJsException won't occur - dispatchError(OT.ExceptionCodes.UNABLE_TO_FORCE_DISCONNECT, - notPermittedErrorMsg, completionHandler); - } - }; - - /** - * Forces the publisher of the specified stream to stop publishing the stream. - * - *

- * Calling this method causes the Session object to dispatch a streamDestroyed - * event on all clients that are subscribed to the stream (including the client that is - * publishing the stream). The reason property of the StreamEvent object is - * set to "forceUnpublished". - *

- *

- * The OT object dispatches an exception event if the user's role - * does not include permissions required to force other users to unpublish. - * You define a user's role when you create the user token using the generate_token() - * method using the - * OpenTok - * server-side libraries or the Dashboard - * page. - * You pass the token string as a parameter of the connect() method of the Session - * object. See ExceptionEvent and - * OT.on(). - *

- * - *
Events dispatched:
- * - *

- * exception (ExceptionEvent) — - * The user's role does not allow forcing other users to unpublish. - *

- *

- * streamDestroyed (StreamEvent) — - * The stream has been unpublished. The Session object dispatches this on all clients - * subscribed to the stream, as well as on the publisher's client. - *

- * - * @param {Stream} stream The stream to be unpublished. - * - * @param {Function} completionHandler (Optional) A function to be called when the call to the - * forceUnpublish() method succeeds or fails. This function takes one parameter - * — error. On success, the completionHandler function is - * not passed any arguments. On error, the function is passed an error object - * parameter. The error object, defined by the Error - * class, has two properties: code (an integer) - * and message (a string), which identify the cause of the failure. Calling - * forceUnpublish() fails if the role assigned to your token is not "moderator"; - * in this case error.code is set to 1530. The following code adds a - * completionHandler when calling the forceUnpublish() method: - *
-  * session.forceUnpublish(stream, function (error) {
-  *   if (error) {
-  *       console.log(error);
-  *     } else {
-  *       console.log("Connection forced to disconnect: " + connection.id);
-  *     }
-  *   });
-  * 
- * - * @method #forceUnpublish - * @memberOf Session - */ - this.forceUnpublish = function(streamOrStreamId, completionHandler) { - if (this.isNot('connected')) { - var notConnectedErrorMsg = 'Cannot call forceUnpublish(). You are not ' + - 'connected to the session.'; - dispatchError(OT.ExceptionCodes.NOT_CONNECTED, notConnectedErrorMsg, completionHandler); - return; - } - - var notPermittedErrorMsg = 'This token does not allow forceUnpublish. ' + - 'The role must be at least `moderator` to enable this functionality'; - - if (permittedTo('forceUnpublish')) { - var stream = typeof streamOrStreamId === 'string' ? - this.streams.get(streamOrStreamId) : streamOrStreamId; - - _socket.forceUnpublish(stream.id, function(err) { - if (err) { - dispatchError(OT.ExceptionCodes.UNABLE_TO_FORCE_UNPUBLISH, - notPermittedErrorMsg, completionHandler); - } else if (completionHandler && OT.$.isFunction(completionHandler)) { - completionHandler.apply(null, arguments); - } - }); - } else { - // if this throws an error the handleJsException won't occur - dispatchError(OT.ExceptionCodes.UNABLE_TO_FORCE_UNPUBLISH, - notPermittedErrorMsg, completionHandler); - } - }; - - this.getStateManager = function() { - OT.warn('Fixme: Have not implemented session.getStateManager'); - }; - - this.isConnected = function() { - return this.is('connected'); - }; - - this.capabilities = new OT.Capabilities([]); - - /** - * Dispatched when an archive recording of the session starts. - * - * @name archiveStarted - * @event - * @memberof Session - * @see ArchiveEvent - * @see Archiving overview. - */ - - /** - * Dispatched when an archive recording of the session stops. - * - * @name archiveStopped - * @event - * @memberof Session - * @see ArchiveEvent - * @see Archiving overview. - */ - - /** - * A new client (including your own) has connected to the session. - * @name connectionCreated - * @event - * @memberof Session - * @see ConnectionEvent - * @see OT.initSession() - */ - - /** - * A client, other than your own, has disconnected from the session. - * @name connectionDestroyed - * @event - * @memberof Session - * @see ConnectionEvent - */ - - /** - * The page has connected to an OpenTok session. This event is dispatched asynchronously - * in response to a successful call to the connect() method of a Session - * object. Before calling the connect() method, initialize the session by - * calling the OT.initSession() method. For a code example and more details, - * see Session.connect(). - * @name sessionConnected - * @event - * @memberof Session - * @see SessionConnectEvent - * @see Session.connect() - * @see OT.initSession() - */ - - /** - * The client has disconnected from the session. This event may be dispatched asynchronously - * in response to a successful call to the disconnect() method of the Session object. - * The event may also be disptached if a session connection is lost inadvertantly, as in the case - * of a lost network connection. - *

- * The default behavior is that all Subscriber objects are unsubscribed and removed from the - * HTML DOM. Each Subscriber object dispatches a destroyed event when the element is - * removed from the HTML DOM. If you call the preventDefault() method in the event - * listener for the sessionDisconnect event, the default behavior is prevented, and - * you can, optionally, clean up Subscriber objects using your own code. +* Starts publishing video (if it is currently not being published) +* when the value is true; stops publishing video +* (if it is currently being published) when the value is false. * - * @name sessionDisconnected - * @event - * @memberof Session - * @see Session.disconnect() - * @see Session.forceDisconnect() - * @see SessionDisconnectEvent - */ +* @param {Boolean} value Whether to start publishing video (true) +* or not (false). +* +* @see OT.initPublisher() +* @see Stream.hasVideo +* @see StreamPropertyChangedEvent +* @method #publishVideo +* @memberOf Publisher +*/ + this.publishVideo = function(value) { + var oldValue = _properties.publishVideo; + _properties.publishVideo = value; + + if (_session && _stream && _properties.publishVideo !== oldValue) { + _stream.setChannelActiveState('video', value); + } + + // We currently do this event if the value of publishVideo has not changed + // This is because the state of the video tracks enabled flag may not match + // the value of publishVideo at this point. This will be tidied up shortly. + if (_webRTCStream) { + var videoTracks = _webRTCStream.getVideoTracks(); + for (var i=0, num=videoTracks.length; istreamCreated - * event. For a code example and more details, see {@link StreamEvent}. - * @name streamCreated - * @event - * @memberof Session - * @see StreamEvent - * @see Session.publish() - */ + * Deletes the Publisher object and removes it from the HTML DOM. + *

+ * The Publisher object dispatches a destroyed event when the DOM + * element is removed. + *

+ * @method #destroy + * @memberOf Publisher + * @return {Publisher} The Publisher. + */ + this.destroy = function(/* unused */ reason, quiet) { + if (_state.isDestroyed()) return; + _state.set('Destroyed'); - /** - * A stream from another client has stopped publishing to the session. - *

- * The default behavior is that all Subscriber objects that are subscribed to the stream are - * unsubscribed and removed from the HTML DOM. Each Subscriber object dispatches a - * destroyed event when the element is removed from the HTML DOM. If you call the - * preventDefault() method in the event listener for the - * streamDestroyed event, the default behavior is prevented and you can clean up - * Subscriber objects using your own code. See - * Session.getSubscribersForStream(). - *

- * For streams published by your own client, the Publisher object dispatches a - * streamDestroyed event. - *

- * For a code example and more details, see {@link StreamEvent}. - * @name streamDestroyed - * @event - * @memberof Session - * @see StreamEvent - */ + reset(); - /** - * A stream has started or stopped publishing audio or video (see - * Publisher.publishAudio() and - * Publisher.publishVideo()); or the - * videoDimensions property of the Stream - * object has changed (see Stream.videoDimensions). - *

- * Note that a subscriber's video can be disabled or enabled for reasons other than the - * publisher disabling or enabling it. A Subscriber object dispatches videoDisabled - * and videoEnabled events in all conditions that cause the subscriber's stream - * to be disabled or enabled. - * - * @name streamPropertyChanged - * @event - * @memberof Session - * @see StreamPropertyChangedEvent - * @see Publisher.publishAudio() - * @see Publisher.publishVideo() - * @see Stream.hasAudio - * @see Stream.hasVideo - * @see Stream.videoDimensions - * @see Subscriber videoDisabled event - * @see Subscriber videoEnabled event - */ + if (quiet !== true) { + this.dispatchEvent( + new OT.DestroyedEvent( + OT.Event.names.PUBLISHER_DESTROYED, + this, + reason + ), + OT.$.bind(this.off, this) + ); + } - /** - * A signal was received from the session. The SignalEvent - * class defines this event object. It includes the following properties: - *

    - *
  • data — (String) The data string sent with the signal (if there - * is one).
  • - *
  • from — (Connection) The Connection - * corresponding to the client that sent with the signal.
  • - *
  • type — (String) The type assigned to the signal (if there is - * one).
  • - *
- *

- * You can register to receive all signals sent in the session, by adding an event handler - * for the signal event. For example, the following code adds an event handler - * to process all signals sent in the session: - *

-	 * session.on("signal", function(event) {
-	 *   console.log("Signal sent from connection: " + event.from.id);
-	 *   console.log("Signal data: " + event.data);
-	 * });
-	 * 
- *

You can register for signals of a specfied type by adding an event handler for the - * signal:type event (replacing type with the actual type string - * to filter on). - * - * @name signal - * @event - * @memberof Session - * @see Session.signal() - * @see SignalEvent - * @see signal:type event - */ - - /** - * A signal of the specified type was received from the session. The - * SignalEvent class defines this event object. - * It includes the following properties: - *

    - *
  • data — (String) The data string sent with the signal.
  • - *
  • from — (Connection) The Connection - * corresponding to the client that sent with the signal.
  • - *
  • type — (String) The type assigned to the signal (if there is one). - *
  • - *
- *

- * You can register for signals of a specfied type by adding an event handler for the - * signal:type event (replacing type with the actual type string - * to filter on). For example, the following code adds an event handler for signals of - * type "foo": - *

-	 * session.on("signal:foo", function(event) {
-	 *   console.log("foo signal sent from connection " + event.from.id);
-	 *   console.log("Signal data: " + event.data);
-	 * });
-	 * 
- *

- * You can register to receive all signals sent in the session, by adding an event - * handler for the signal event. - * - * @name signal:type - * @event - * @memberof Session - * @see Session.signal() - * @see SignalEvent - * @see signal event - */ + return this; }; -})(window); -(function() { - - var txt = function(text) { - return document.createTextNode(text); + /** + * @methodOf Publisher + * @private + */ + this.disconnect = function() { + // Close the connection to each of our subscribers + for (var fromConnectionId in _peerConnections) { + this.cleanupSubscriber(fromConnectionId); + } }; - var el = function(attr, children, tagName) { - var el = OT.$.createElement(tagName || 'div', attr, children); - el.on = OT.$.bind(OT.$.on, OT.$, el); - return el; + this.cleanupSubscriber = function(fromConnectionId) { + var pc = _peerConnections[fromConnectionId]; + + if (pc) { + pc.destroy(); + delete _peerConnections[fromConnectionId]; + + logAnalyticsEvent('disconnect', 'PeerConnection', + {subscriberConnection: fromConnectionId}); + } }; - function DevicePickerController(opts) { - var destroyExistingPublisher, - publisher, - devicesById; - this.change = OT.$.bind(function() { - destroyExistingPublisher(); + this.processMessage = function(type, fromConnection, message) { + OT.debug('OT.Publisher.processMessage: Received ' + type + ' from ' + fromConnection.id); + OT.debug(message); - var settings; + switch (type) { + case 'unsubscribe': + this.cleanupSubscriber(message.content.connection.id); + break; - this.pickedDevice = devicesById[opts.selectTag.value]; + default: + var peerConnection = createPeerConnectionForRemote(fromConnection); + peerConnection.processMessage(type, message); + } + }; - if(!this.pickedDevice) { - console.log('No device for', opts.mode, opts.selectTag.value); - return; - } + /** + * Returns the base-64-encoded string of PNG data representing the Publisher video. + * + *

You can use the string as the value for a data URL scheme passed to the src parameter of + * an image file, as in the following:

+ * + *
+  *  var imgData = publisher.getImgData();
+  *
+  *  var img = document.createElement("img");
+  *  img.setAttribute("src", "data:image/png;base64," + imgData);
+  *  var imgWin = window.open("about:blank", "Screenshot");
+  *  imgWin.document.write("<body></body>");
+  *  imgWin.document.body.appendChild(img);
+  * 
+ * + * @method #getImgData + * @memberOf Publisher + * @return {String} The base-64 encoded string. Returns an empty string if there is no video. + */ + + this.getImgData = function() { + if (!_loaded) { + OT.error('OT.Publisher.getImgData: Cannot getImgData before the Publisher is publishing.'); + + return null; + } + + return _targetElement.imgData(); + }; + + + // API Compatibility layer for Flash Publisher, this could do with some tidyup. + this._ = { + publishToSession: OT.$.bind(function(session) { + // Add session property to Publisher + this.session = _session = session; + + var createStream = function() { + + var streamWidth, + streamHeight; + + // Bail if this.session is gone, it means we were unpublished + // before createStream could finish. + if (!_session) return; + + _state.set('PublishingToSession'); + + var onStreamRegistered = OT.$.bind(function(err, streamId, message) { + if (err) { + // @todo we should respect err.code here and translate it to the local + // client equivalent. + var errorCode = OT.ExceptionCodes.UNABLE_TO_PUBLISH; + var payload = { + reason: 'Publish', + code: errorCode, + message: err.message + }; + logConnectivityEvent('Failure', payload); + if (_state.isAttemptingToPublish()) { + this.trigger('publishComplete', new OT.Error(errorCode, err.message)); + } + return; + } + + this.streamId = _streamId = streamId; + _iceServers = OT.Raptor.parseIceServers(message); + }, this); + + // We set the streamWidth and streamHeight to be the minimum of the requested + // resolution and the actual resolution. + if (_properties.videoDimensions) { + streamWidth = Math.min(_properties.videoDimensions.width, + _targetElement.videoWidth() || 640); + streamHeight = Math.min(_properties.videoDimensions.height, + _targetElement.videoHeight() || 480); + } else { + streamWidth = _targetElement.videoWidth() || 640; + streamHeight = _targetElement.videoHeight() || 480; + } + + var streamChannels = []; + + if (!(_properties.videoSource === null || _properties.videoSource === false)) { + streamChannels.push(new OT.StreamChannel({ + id: 'video1', + type: 'video', + active: _properties.publishVideo, + orientation: OT.VideoOrientation.ROTATED_NORMAL, + frameRate: _properties.frameRate, + width: streamWidth, + height: streamHeight, + source: _isScreenSharing ? 'screen' : 'camera', + fitMode: _properties.fitMode + })); + } + + if (!(_properties.audioSource === null || _properties.audioSource === false)) { + streamChannels.push(new OT.StreamChannel({ + id: 'audio1', + type: 'audio', + active: _properties.publishAudio + })); + } + + session._.streamCreate(_properties.name || '', _properties.audioFallbackEnabled, + streamChannels, onStreamRegistered); - settings = { - insertMode: 'append', - name: this.pickedDevice.label, - audioSource: null, - videoSource: null, - width: 220, - height: 165 }; - settings[opts.mode] = this.pickedDevice.deviceId; + if (_loaded) createStream.call(this); + else this.on('initSuccess', createStream, this); - console.log('initPublisher', opts.previewTag, settings); - var pub = OT.initPublisher(opts.previewTag, settings); + logConnectivityEvent('Attempt', {streamType: 'WebRTC'}); - pub.on({ - accessDialogOpened: function(event) { - event.preventDefault(); - }, - accessDialogClosed: function() { - }, - accessAllowed: function() { - }, - accessDenied: function(event) { - event.preventDefault(); - } - }); + return this; + }, this), - publisher = pub; - }, this); - - this.cleanup = destroyExistingPublisher = function() { - if(publisher) { - publisher.destroy(); - publisher = void 0; + unpublishFromSession: OT.$.bind(function(session, reason) { + if (!_session || session.id !== _session.id) { + OT.warn('The publisher ' + _guid + ' is trying to unpublish from a session ' + + session.id + ' it is not attached to (it is attached to ' + + (_session && _session.id || 'no session') + ')'); + return this; } - }; - var disableSelector = function (opt, str) { - opt.innerHTML = ''; - opt.appendChild(el({}, txt(str), 'option')); - opt.setAttribute('disabled', ''); - }; - - var addDevice = function (device) { - devicesById[device.deviceId] = device; - return el({ value: device.deviceId }, txt(device.label), 'option'); - }; - - this.setDeviceList = OT.$.bind(function (devices) { - opts.selectTag.innerHTML = ''; - devicesById = {}; - if(devices.length > 0) { - devices.map(addDevice).map(OT.$.bind(opts.selectTag.appendChild, opts.selectTag)); - opts.selectTag.removeAttribute('disabled'); - } else { - disableSelector(opts.selectTag, 'No devices'); + if (session.isConnected() && this.stream) { + session._.streamDestroy(this.stream.id); } - this.change(); - }, this); - this.setLoading = function() { - disableSelector(opts.selectTag, 'Loading...'); - }; + // Disconnect immediately, rather than wait for the WebSocket to + // reply to our destroyStream message. + this.disconnect(); + this.session = _session = null; - OT.$.on(opts.selectTag, 'change', this.change); + // We're back to being a stand-alone publisher again. + if (!_state.isDestroyed()) _state.set('MediaBound'); + + if(_connectivityAttemptPinger) { + _connectivityAttemptPinger.stop(); + } + logAnalyticsEvent('unpublish', 'Success', {sessionId: session.id}); + + this._.streamDestroyed(reason); + + return this; + }, this), + + streamDestroyed: OT.$.bind(function(reason) { + if(OT.$.arrayIndexOf(['reset'], reason) < 0) { + var event = new OT.StreamEvent('streamDestroyed', _stream, reason, true); + var defaultAction = OT.$.bind(function() { + if(!event.isDefaultPrevented()) { + this.destroy(); + } + }, this); + this.dispatchEvent(event, defaultAction); + } + }, this), + + + archivingStatus: OT.$.bind(function(status) { + if(_chrome) { + _chrome.archive.setArchiving(status); + } + + return status; + }, this), + + webRtcStream: function() { + return _webRTCStream; + } + }; + + this.detectDevices = function() { + OT.warn('Fixme: Haven\'t implemented detectDevices'); + }; + + this.detectMicActivity = function() { + OT.warn('Fixme: Haven\'t implemented detectMicActivity'); + }; + + this.getEchoCancellationMode = function() { + OT.warn('Fixme: Haven\'t implemented getEchoCancellationMode'); + return 'fullDuplex'; + }; + + this.setMicrophoneGain = function() { + OT.warn('Fixme: Haven\'t implemented setMicrophoneGain'); + }; + + this.getMicrophoneGain = function() { + OT.warn('Fixme: Haven\'t implemented getMicrophoneGain'); + return 0.5; + }; + + this.setCamera = function() { + OT.warn('Fixme: Haven\'t implemented setCamera'); + }; + + this.setMicrophone = function() { + OT.warn('Fixme: Haven\'t implemented setMicrophone'); + }; + + + // Platform methods: + + this.guid = function() { + return _guid; + }; + + this.videoElement = function() { + return _targetElement.domElement(); + }; + + this.setStream = assignStream; + + this.isWebRTC = true; + + this.isLoading = function() { + return _widgetView && _widgetView.loading(); + }; + + this.videoWidth = function() { + return _targetElement.videoWidth(); + }; + + this.videoHeight = function() { + return _targetElement.videoHeight(); + }; + + // Make read-only: element, guid, _.webRtcStream + + this.on('styleValueChanged', updateChromeForStyleChange, this); + _state = new OT.PublishingState(stateChangeFailed); + + this.accessAllowed = false; + +/** +* Dispatched when the user has clicked the Allow button, granting the +* app access to the camera and microphone. The Publisher object has an +* accessAllowed property which indicates whether the user +* has granted access to the camera and microphone. +* @see Event +* @name accessAllowed +* @event +* @memberof Publisher +*/ + +/** +* Dispatched when the user has clicked the Deny button, preventing the +* app from having access to the camera and microphone. +* @see Event +* @name accessDenied +* @event +* @memberof Publisher +*/ + +/** +* Dispatched when the Allow/Deny dialog box is opened. (This is the dialog box in which +* the user can grant the app access to the camera and microphone.) +* @see Event +* @name accessDialogOpened +* @event +* @memberof Publisher +*/ + +/** +* Dispatched when the Allow/Deny box is closed. (This is the dialog box in which the +* user can grant the app access to the camera and microphone.) +* @see Event +* @name accessDialogClosed +* @event +* @memberof Publisher +*/ + + /** + * Dispatched periodically to indicate the publisher's audio level. The event is dispatched + * up to 60 times per second, depending on the browser. The audioLevel property + * of the event is audio level, from 0 to 1.0. See {@link AudioLevelUpdatedEvent} for more + * information. + *

+ * The following example adjusts the value of a meter element that shows volume of the + * publisher. Note that the audio level is adjusted logarithmically and a moving average + * is applied: + *

+ *

+  * var movingAvg = null;
+  * publisher.on('audioLevelUpdated', function(event) {
+  *   if (movingAvg === null || movingAvg <= event.audioLevel) {
+  *     movingAvg = event.audioLevel;
+  *   } else {
+  *     movingAvg = 0.7 * movingAvg + 0.3 * event.audioLevel;
+  *   }
+  *
+  *   // 1.5 scaling to map the -30 - 0 dBm range to [0,1]
+  *   var logLevel = (Math.log(movingAvg) / Math.LN10) / 1.5 + 1;
+  *   logLevel = Math.min(Math.max(logLevel, 0), 1);
+  *   document.getElementById('publisherMeter').value = logLevel;
+  * });
+  * 
+ *

This example shows the algorithm used by the default audio level indicator displayed + * in an audio-only Publisher. + * + * @name audioLevelUpdated + * @event + * @memberof Publisher + * @see AudioLevelUpdatedEvent + */ + +/** + * The publisher has started streaming to the session. + * @name streamCreated + * @event + * @memberof Publisher + * @see StreamEvent + * @see Session.publish() + */ + +/** + * The publisher has stopped streaming to the session. The default behavior is that + * the Publisher object is removed from the HTML DOM). The Publisher object dispatches a + * destroyed event when the element is removed from the HTML DOM. If you call the + * preventDefault() method of the event object in the event listener, the default + * behavior is prevented, and you can, optionally, retain the Publisher for reuse or clean it up + * using your own code. + * @name streamDestroyed + * @event + * @memberof Publisher + * @see StreamEvent + */ + +/** +* Dispatched when the Publisher element is removed from the HTML DOM. When this event +* is dispatched, you may choose to adjust or remove HTML DOM elements related to the publisher. +* @name destroyed +* @event +* @memberof Publisher +*/ + +/** +* Dispatched when the video dimensions of the video change. This can only occur in when the +* stream.videoType property is set to "screen" (for a screen-sharing +* video stream), and the user resizes the window being captured. +* @name videoDimensionsChanged +* @event +* @memberof Publisher +*/ + +/** + * The user has stopped screen-sharing for the published stream. This event is only dispatched + * for screen-sharing video streams. + * @name mediaStopped + * @event + * @memberof Publisher + * @see StreamEvent + */ +}; + +// Helper function to generate unique publisher ids +OT.Publisher.nextId = OT.$.uuid; + +// tb_require('../../conf/properties.js') +// tb_require('../ot.js') +// tb_require('./session.js') +// tb_require('./publisher.js') + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global OT */ + + +/** +* The first step in using the OpenTok API is to call the OT.initSession() +* method. Other methods of the OT object check for system requirements and set up error logging. +* +* @class OT +*/ + +/** +*

+* Initializes and returns the local session object for a specified session ID. +*

+*

+* You connect to an OpenTok session using the connect() method +* of the Session object returned by the OT.initSession() method. +* Note that calling OT.initSession() does not initiate communications +* with the cloud. It simply initializes the Session object that you can use to +* connect (and to perform other operations once connected). +*

+* +*

+* For an example, see Session.connect(). +*

+* +* @method OT.initSession +* @memberof OT +* @param {String} apiKey Your OpenTok API key (see the +* OpenTok dashboard). +* @param {String} sessionId The session ID identifying the OpenTok session. For more +* information, see Session creation. +* @returns {Session} The session object through which all further interactions with +* the session will occur. +*/ +OT.initSession = function(apiKey, sessionId) { + + if(sessionId == null) { + sessionId = apiKey; + apiKey = null; } - OT.HardwareSetup = function(targetElement, options, callback) { + var session = OT.sessions.get(sessionId); - var camera, - microphone, - setupDOM, - setState; + if (!session) { + session = new OT.Session(apiKey, sessionId); + OT.sessions.add(session); + } - setState = OT.$.statable(this, ['getDevices', 'chooseDevices', 'destroyed'], 'getDevices'); + return session; +}; - this.audioSource = function() { - return microphone && microphone.pickedDevice; - }; +/** +*

+* Initializes and returns a Publisher object. You can then pass this Publisher +* object to Session.publish() to publish a stream to a session. +*

+*

+* Note: If you intend to reuse a Publisher object created using +* OT.initPublisher() to publish to different sessions sequentially, +* call either Session.disconnect() or Session.unpublish(). +* Do not call both. Then call the preventDefault() method of the +* streamDestroyed or sessionDisconnected event object to prevent the +* Publisher object from being removed from the page. +*

+* +* @param {Object} targetElement (Optional) The DOM element or the id attribute of the +* existing DOM element used to determine the location of the Publisher video in the HTML DOM. See +* the insertMode property of the properties parameter. If you do not +* specify a targetElement, the application appends a new DOM element to the HTML +* body. +* +*

+* The application throws an error if an element with an ID set to the +* targetElement value does not exist in the HTML DOM. +*

+* +* @param {Object} properties (Optional) This object contains the following properties (each of which +* are optional): +*

+*
    +*
  • +* audioFallbackEnabled (String) — Whether the stream will use the +* audio-fallback feature (true) or not (false). The audio-fallback +* feature is available in sessions that use the the OpenTok Media Router. With the audio-fallback +* feature enabled (the default), when the server determines that a stream's quality has degraded +* significantly for a specific subscriber, it disables the video in that subscriber in order to +* preserve audio quality. For streams that use a camera as a video source, the default setting is +* true (the audio-fallback feature is enabled). The default setting is +* false (the audio-fallback feature is disabled) for screen-sharing streams, which +* have the videoSource set to "screen" in the +* OT.initPublisher() options. For more information, see the Subscriber +* videoDisabled event and +* the OpenTok Media +* Router and media modes. +*
  • +*
  • +* audioSource (String) — The ID of the audio input device (such as a +* microphone) to be used by the publisher. You can obtain a list of available devices, including +* audio input devices, by calling the OT.getDevices() method. Each +* device listed by the method has a unique device ID. If you pass in a device ID that does not +* match an existing audio input device, the call to OT.initPublisher() fails with an +* error (error code 1500, "Unable to Publish") passed to the completion handler function. +*

    +* If you set this property to null or false, the browser does not +* request access to the microphone, and no audio is published. +*

    +*
  • +*
  • +* fitMode (String) — Determines how the video is displayed if the its +* dimensions do not match those of the DOM element. You can set this property to one of the +* following values: +*

    +*

      +*
    • +* "cover" — The video is cropped if its dimensions do not match those of +* the DOM element. This is the default setting for screen-sharing videos. +*
    • +*
    • +* "contain" — The video is letter-boxed if its dimensions do not match +* those of the DOM element. This is the default setting for videos publishing a camera feed. +*
    • +*
    +*
  • +*
  • +* frameRate (Number) — The desired frame rate, in frames per second, +* of the video. Valid values are 30, 15, 7, and 1. The published stream will use the closest +* value supported on the publishing client. The frame rate can differ slightly from the value +* you set, depending on the browser of the client. And the video will only use the desired +* frame rate if the client configuration supports it. +*

    If the publisher specifies a frame rate, the actual frame rate of the video stream +* is set as the frameRate property of the Stream object, though the actual frame rate +* will vary based on changing network and system conditions. If the developer does not specify a +* frame rate, this property is undefined. +*

    +* For sessions that use the OpenTok Media Router (sessions with +* the media mode +* set to routed, lowering the frame rate or lowering the resolution reduces +* the maximum bandwidth the stream can use. However, in sessions with the media mode set to +* relayed, lowering the frame rate or resolution may not reduce the stream's bandwidth. +*

    +*

    +* You can also restrict the frame rate of a Subscriber's video stream. To restrict the frame rate +* a Subscriber, call the restrictFrameRate() method of the subscriber, passing in +* true. +* (See Subscriber.restrictFrameRate().) +*

    +*
  • +*
  • +* height (Number) — The desired height, in pixels, of the +* displayed Publisher video stream (default: 198). Note: Use the +* height and width properties to set the dimensions +* of the publisher video; do not set the height and width of the DOM element +* (using CSS). +*
  • +*
  • +* insertMode (String) — Specifies how the Publisher object will be +* inserted in the HTML DOM. See the targetElement parameter. This string can +* have the following values: +*
      +*
    • "replace" — The Publisher object replaces contents of the +* targetElement. This is the default.
    • +*
    • "after" — The Publisher object is a new element inserted after +* the targetElement in the HTML DOM. (Both the Publisher and targetElement have the +* same parent element.)
    • +*
    • "before" — The Publisher object is a new element inserted before +* the targetElement in the HTML DOM. (Both the Publisher and targetElement have the same +* parent element.)
    • +*
    • "append" — The Publisher object is a new element added as a child +* of the targetElement. If there are other child elements, the Publisher is appended as +* the last child element of the targetElement.
    • +*
    +*
  • +*
  • +* maxResolution (Object) — Sets the maximum resoultion to stream. +* This setting only applies to when the videoSource property is set to +* "screen" (when the publisher is screen-sharing). The resolution of the +* stream will match the captured screen region unless the region is greater than the +* maxResolution setting. Set this to an object that has two properties: +* width and height (both numbers). The maximum value for each of +* the width and height properties is 1920, and the minimum value +* is 10. +*
  • +*
  • +* mirror (Boolean) — Whether the publisher's video image +* is mirrored in the publisher's page. The default value is true +* (the video image is mirrored), except when the videoSource property is set +* to "screen" (in which case the default is false). This property +* does not affect the display on subscribers' views of the video. +*
  • +*
  • +* name (String) — The name for this stream. The name appears at +* the bottom of Subscriber videos. The default value is "" (an empty string). Setting +* this to a string longer than 1000 characters results in an runtime exception. +*
  • +*
  • +* publishAudio (Boolean) — Whether to initially publish audio +* for the stream (default: true). This setting applies when you pass +* the Publisher object in a call to the Session.publish() method. +*
  • +*
  • +* publishVideo (Boolean) — Whether to initially publish video +* for the stream (default: true). This setting applies when you pass +* the Publisher object in a call to the Session.publish() method. +*
  • +*
  • +* resolution (String) — The desired resolution of the video. The format +* of the string is "widthxheight", where the width and height are represented in +* pixels. Valid values are "1280x720", "640x480", and +* "320x240". The published video will only use the desired resolution if the +* client configuration supports it. +*

    +* The requested resolution of a video stream is set as the videoDimensions.width and +* videoDimensions.height properties of the Stream object. +*

    +*

    +* The default resolution for a stream (if you do not specify a resolution) is 640x480 pixels. +* If the client system cannot support the resolution you requested, the the stream will use the +* next largest setting supported. +*

    +*

    +* For sessions that use the OpenTok Media Router (sessions with the +* media mode +* set to routed, lowering the frame rate or lowering the resolution reduces the maximum bandwidth +* the stream can use. However, in sessions that have the media mode set to relayed, lowering the +* frame rate or resolution may not reduce the stream's bandwidth. +*

    +*
  • +*
  • +* style (Object) — An object containing properties that define the initial +* appearance of user interface controls of the Publisher. The style object includes +* the following properties: +*
      +*
    • audioLevelDisplayMode (String) — How to display the audio level +* indicator. Possible values are: "auto" (the indicator is displayed when the +* video is disabled), "off" (the indicator is not displayed), and +* "on" (the indicator is always displayed).
    • +* +*
    • backgroundImageURI (String) — A URI for an image to display as +* the background image when a video is not displayed. (A video may not be displayed if +* you call publishVideo(false) on the Publisher object). You can pass an http +* or https URI to a PNG, JPEG, or non-animated GIF file location. You can also use the +* data URI scheme (instead of http or https) and pass in base-64-encrypted +* PNG data, such as that obtained from the +* Publisher.getImgData() method. For example, +* you could set the property to "data:VBORw0KGgoAA...", where the portion of the +* string after "data:" is the result of a call to +* Publisher.getImgData(). If the URL or the image data is invalid, the property +* is ignored (the attempt to set the image fails silently). +*

      +* Note that in Internet Explorer 8 (using the OpenTok Plugin for Internet Explorer), +* you cannot set the backgroundImageURI style to a string larger than 32 kB. +* This is due to an IE 8 limitation on the size of URI strings. Due to this limitation, +* you cannot set the backgroundImageURI style to a string obtained with the +* getImgData() method. +*

    • +* +*
    • buttonDisplayMode (String) — How to display the microphone controls +* Possible values are: "auto" (controls are displayed when the stream is first +* displayed and when the user mouses over the display), "off" (controls are not +* displayed), and "on" (controls are always displayed).
    • +* +*
    • nameDisplayMode (String) — Whether to display the stream name. +* Possible values are: "auto" (the name is displayed when the stream is first +* displayed and when the user mouses over the display), "off" (the name is not +* displayed), and "on" (the name is always displayed).
    • +*
    +*
  • +*
  • +* videoSource (String) — The ID of the video input device (such as a +* camera) to be used by the publisher. You can obtain a list of available devices, including +* video input devices, by calling the OT.getDevices() method. Each +* device listed by the method has a unique device ID. If you pass in a device ID that does not +* match an existing video input device, the call to OT.initPublisher() fails with an +* error (error code 1500, "Unable to Publish") passed to the completion handler function. +*

    +* If you set this property to null or false, the browser does not +* request access to the camera, and no video is published. In a voice-only call, set this +* property to null or false for each Publisher. +*

    +*

    +* Set this property to "screen" to publish a screen-sharing stream. Call +* OT.checkScreenSharingCapability() to check +* if screen sharing is supported. When you set the videoSource property to +* "screen", the following are default values for other properties: +* audioFallbackEnabled == false, +* maxResolution == {width: 1920, height: 1920}, mirror == false, +* scaleMode == "fit". Also, the default scaleMode setting for +* subscribers to the stream is "fit". +*

  • +*
  • +* width (Number) — The desired width, in pixels, of the +* displayed Publisher video stream (default: 264). Note: Use the +* height and width properties to set the dimensions +* of the publisher video; do not set the height and width of the DOM element +* (using CSS). +*
  • +*
+* @param {Function} completionHandler (Optional) A function to be called when the method succeeds +* or fails in initializing a Publisher object. This function takes one parameter — +* error. On success, the error object is set to null. On +* failure, the error object has two properties: code (an integer) and +* message (a string), which identify the cause of the failure. The method succeeds +* when the user grants access to the camera and microphone. The method fails if the user denies +* access to the camera and microphone. The completionHandler function is called +* before the Publisher dispatches an accessAllowed (success) event or an +* accessDenied (failure) event. +*

+* The following code adds a completionHandler when calling the +* OT.initPublisher() method: +*

+*
+* var publisher = OT.initPublisher('publisher', null, function (error) {
+*   if (error) {
+*     console.log(error);
+*   } else {
+*     console.log("Publisher initialized.");
+*   }
+* });
+* 
+* +* @returns {Publisher} The Publisher object. +* @see for audio input + * devices or "videoInput" for video input devices. + *

+ * The deviceId property is a unique ID for the device. You can pass + * the deviceId in as the audioSource or videoSource + * property of the the options parameter of the + * OT.initPublisher() method. + *

+ * The label property identifies the device. The label + * property is set to an empty string if the user has not previously granted access to + * a camera and microphone. In HTTP, the user must have granted access to a camera and + * microphone in the current page (for example, in response to a call to + * OT.initPublisher()). In HTTPS, the user must have previously granted access + * to the camera and microphone in the current page or in a page previously loaded from the + * domain. + * + * + * @see OT.initPublisher() + * @method OT.getDevices + * @memberof OT + */ +OT.getDevices = function(callback) { + OT.$.getMediaDevices(callback); +}; + + + +OT.reportIssue = function(){ + OT.warn('ToDo: haven\'t yet implemented OT.reportIssue'); +}; + +OT.components = {}; + + +/** + * This method is deprecated. Use on() or once() instead. + * + *

+ * Registers a method as an event listener for a specific event. + *

+ * + *

+ * The OT object dispatches one type of event — an exception event. The + * following code adds an event listener for the exception event: + *

+ * + *
+ * OT.addEventListener("exception", exceptionHandler);
+ *
+ * function exceptionHandler(event) {
+ *    alert("exception event. \n  code == " + event.code + "\n  message == " + event.message);
+ * }
+ * 
+ * + *

+ * If a handler is not registered for an event, the event is ignored locally. If the event + * listener function does not exist, the event is ignored locally. + *

+ *

+ * Throws an exception if the listener name is invalid. + *

+ * + * @param {String} type The string identifying the type of event. + * + * @param {Function} listener The function to be invoked when the OT object dispatches the event. + * @see on() + * @see once() + * @memberof OT + * @method addEventListener + * + */ + +/** + * This method is deprecated. Use off() instead. + * + *

+ * Removes an event listener for a specific event. + *

+ * + *

+ * Throws an exception if the listener name is invalid. + *

+ * + * @param {String} type The string identifying the type of event. + * + * @param {Function} listener The event listener function to remove. + * + * @see off() + * @memberof OT + * @method removeEventListener + */ + + +/** +* Adds an event handler function for one or more events. +* +*

+* The OT object dispatches one type of event — an exception event. The following +* code adds an event +* listener for the exception event: +*

+* +*
+* OT.on("exception", function (event) {
+*   // This is the event handler.
+* });
+* 
+* +*

You can also pass in a third context parameter (which is optional) to define the +* value of +* this in the handler method:

+* +*
+* OT.on("exception",
+*   function (event) {
+*     // This is the event handler.
+*   }),
+*   session
+* );
+* 
+* +*

+* If you do not add a handler for an event, the event is ignored locally. +*

+* +* @param {String} type The string identifying the type of event. +* @param {Function} handler The handler function to process the event. This function takes the event +* object as a parameter. +* @param {Object} context (Optional) Defines the value of this in the event handler +* function. +* +* @memberof OT +* @method on +* @see off() +* @see once() +* @see Events +*/ + +/** +* Adds an event handler function for an event. Once the handler is called, the specified handler +* method is +* removed as a handler for this event. (When you use the OT.on() method to add an event +* handler, the handler +* is not removed when it is called.) The OT.once() method is the equivilent of +* calling the OT.on() +* method and calling OT.off() the first time the handler is invoked. +* +*

+* The following code adds a one-time event handler for the exception event: +*

+* +*
+* OT.once("exception", function (event) {
+*   console.log(event);
+* }
+* 
+* +*

You can also pass in a third context parameter (which is optional) to define the +* value of +* this in the handler method:

+* +*
+* OT.once("exception",
+*   function (event) {
+*     // This is the event handler.
+*   },
+*   session
+* );
+* 
+* +*

+* The method also supports an alternate syntax, in which the first parameter is an object that is a +* hash map of +* event names and handler functions and the second parameter (optional) is the context for this in +* each handler: +*

+*
+* OT.once(
+*   {exeption: function (event) {
+*     // This is the event handler.
+*     }
+*   },
+*   session
+* );
+* 
+* +* @param {String} type The string identifying the type of event. You can specify multiple event +* names in this string, +* separating them with a space. The event handler will process the first occurence of the events. +* After the first event, +* the handler is removed (for all specified events). +* @param {Function} handler The handler function to process the event. This function takes the event +* object as a parameter. +* @param {Object} context (Optional) Defines the value of this in the event handler +* function. +* +* @memberof OT +* @method once +* @see on() +* @see once() +* @see Events +*/ + + +/** + * Removes an event handler. + * + *

Pass in an event name and a handler method, the handler is removed for that event:

+ * + *
OT.off("exceptionEvent", exceptionEventHandler);
+ * + *

If you pass in an event name and no handler method, all handlers are removed for that + * events:

+ * + *
OT.off("exceptionEvent");
+ * + *

+ * The method also supports an alternate syntax, in which the first parameter is an object that is a + * hash map of + * event names and handler functions and the second parameter (optional) is the context for matching + * handlers: + *

+ *
+ * OT.off(
+ *   {
+ *     exceptionEvent: exceptionEventHandler
+ *   },
+ *   this
+ * );
+ * 
+ * + * @param {String} type (Optional) The string identifying the type of event. You can use a space to + * specify multiple events, as in "eventName1 eventName2 eventName3". If you pass in no + * type value (or other arguments), all event handlers are removed for the object. + * @param {Function} handler (Optional) The event handler function to remove. If you pass in no + * handler, all event handlers are removed for the specified event type. + * @param {Object} context (Optional) If you specify a context, the event handler is + * removed for all specified events and handlers that use the specified context. + * + * @memberof OT + * @method off + * @see on() + * @see once() + * @see Events + */ + +/** + * Dispatched by the OT class when the app encounters an exception. + * Note that you set up an event handler for the exception event by calling the + * OT.on() method. + * + * @name exception + * @event + * @borrows ExceptionEvent#message as this.message + * @memberof OT + * @see ExceptionEvent + */ + + +// tb_require('./helpers/lib/css_loader.js') +// tb_require('./ot/system_requirements.js') +// tb_require('./ot/session.js') +// tb_require('./ot/publisher.js') +// tb_require('./ot/subscriber.js') +// tb_require('./ot/archive.js') +// tb_require('./ot/connection.js') +// tb_require('./ot/stream.js') +// We want this to be included at the end, just before footer.js + +/* jshint globalstrict: true, strict: false, undef: true, unused: true, + trailing: true, browser: true, smarttabs:true */ +/* global loadCSS, define */ + +// Tidy up everything on unload +OT.onUnload(function() { + OT.publishers.destroy(); + OT.subscribers.destroy(); + OT.sessions.destroy('unloaded'); +}); + +loadCSS(OT.properties.cssURL); // Register as a named AMD module, since TokBox could be concatenated with other // files that may use define, but not via a proper concatenation script that @@ -22274,8 +24115,13 @@ var SDPHelpers = { // way to register. Uppercase TB is used because AMD module names are // derived from file names, and OpenTok is normally delivered in an uppercase // file name. - if (typeof define === 'function' && define.amd) { - define( 'TB', [], function () { return TB; } ); - } +if (typeof define === 'function' && define.amd) { + define( 'TB', [], function () { return TB; } ); +} +// tb_require('./postscript.js') -})(window); +/* jshint ignore:start */ +})(window, window.OT); +/* jshint ignore:end */ + +})(window || exports); \ No newline at end of file diff --git a/browser/components/loop/test/functional/test_1_browser_call.py b/browser/components/loop/test/functional/test_1_browser_call.py index de8d6dee7e7..0985e9ca519 100644 --- a/browser/components/loop/test/functional/test_1_browser_call.py +++ b/browser/components/loop/test/functional/test_1_browser_call.py @@ -129,7 +129,7 @@ class Test1BrowserCall(MarionetteTestCase): def check_remote_video(self): video_wrapper = self.wait_for_element_displayed( By.CSS_SELECTOR, - ".media .OT_subscriber .OT_video-container", 20) + ".media .OT_subscriber .OT_widget-container", 20) video = self.wait_for_subelement_displayed( video_wrapper, By.TAG_NAME, "video") From 4b3a6f3b0713c2dbe60ffee48ab46ca5666f1c57 Mon Sep 17 00:00:00 2001 From: Mike de Boer Date: Fri, 30 Jan 2015 16:01:42 +0000 Subject: [PATCH 002/101] Bug 1093780 Part 2 - Add support for using 'contain' mode for all video streams Loop publishes and resize/ position the elements based on their aspect ratio. r=Standard8 --- .../loop/content/shared/css/conversation.css | 16 -- .../loop/content/shared/js/actions.js | 9 + .../loop/content/shared/js/activeRoomStore.js | 24 +- .../loop/content/shared/js/mixins.js | 210 +++++++++++++++++- .../loop/content/shared/js/otSdkDriver.js | 45 +++- .../loop/content/shared/js/utils.js | 7 + .../content/js/standaloneRoomViews.js | 39 +++- .../content/js/standaloneRoomViews.jsx | 39 +++- 8 files changed, 356 insertions(+), 33 deletions(-) diff --git a/browser/components/loop/content/shared/css/conversation.css b/browser/components/loop/content/shared/css/conversation.css index 15acdda5e6a..fd2007483c2 100644 --- a/browser/components/loop/content/shared/css/conversation.css +++ b/browser/components/loop/content/shared/css/conversation.css @@ -3,11 +3,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* Shared conversation window styles */ -.standalone .video-layout-wrapper, -.conversation .media video { - background-color: #444; -} - .conversation { position: relative; } @@ -673,7 +668,6 @@ html, .fx-embedded, #main, } .standalone { - max-width: 1000px; margin: 0 auto; } } @@ -905,11 +899,6 @@ html, .fx-embedded, #main, width: 75%; } -.standalone .room-conversation .local-stream { - width: 33%; - height: 26.5%; -} - .standalone .room-conversation .conversation-toolbar { background: #000; border: none; @@ -945,11 +934,6 @@ html, .fx-embedded, #main, .standalone .room-conversation .video_wrapper.remote_wrapper { width: 100%; } - .standalone .room-conversation .local-stream { - /* Assumes 4:3 aspect ratio */ - width: 180px; - height: 135px; - } .standalone .conversation-toolbar { height: 38px; padding: 8px; diff --git a/browser/components/loop/content/shared/js/actions.js b/browser/components/loop/content/shared/js/actions.js index 6b1f95ee55d..7e6a1fa3874 100644 --- a/browser/components/loop/content/shared/js/actions.js +++ b/browser/components/loop/content/shared/js/actions.js @@ -176,6 +176,15 @@ loop.shared.actions = (function() { MediaConnected: Action.define("mediaConnected", { }), + /** + * Used for notifying that the dimensions of a stream just changed. Also + * dispatched when a stream connects for the first time. + */ + VideoDimensionsChanged: Action.define("videoDimensionsChanged", { + videoType: String, + dimensions: Object + }), + /** * Used to mute or unmute a stream */ diff --git a/browser/components/loop/content/shared/js/activeRoomStore.js b/browser/components/loop/content/shared/js/activeRoomStore.js index 83a26317304..cf0196b9685 100644 --- a/browser/components/loop/content/shared/js/activeRoomStore.js +++ b/browser/components/loop/content/shared/js/activeRoomStore.js @@ -68,7 +68,9 @@ loop.store.ActiveRoomStore = (function() { // session. 'Used' means at least one call has been placed // with it. Entering and leaving the room without seeing // anyone is not considered as 'used' - used: false + used: false, + localVideoDimensions: {}, + remoteVideoDimensions: {} }; }, @@ -119,7 +121,8 @@ loop.store.ActiveRoomStore = (function() { "remotePeerConnected", "windowUnload", "leaveRoom", - "feedbackComplete" + "feedbackComplete", + "videoDimensionsChanged" ]); }, @@ -477,6 +480,23 @@ loop.store.ActiveRoomStore = (function() { // Note, that we want some values, such as the windowId, so we don't // do a full reset here. this.setStoreState(this.getInitialStoreState()); + }, + + /** + * Handles a change in dimensions of a video stream and updates the store data + * with the new dimensions of a local or remote stream. + * + * @param {sharedActions.VideoDimensionsChanged} actionData + */ + videoDimensionsChanged: function(actionData) { + // NOTE: in the future, when multiple remote video streams are supported, + // we'll need to make this support multiple remotes as well. Good + // starting point for video tiling. + var storeProp = (actionData.isLocal ? "local" : "remote") + "VideoDimensions"; + var nextState = {}; + nextState[storeProp] = this.getStoreState()[storeProp]; + nextState[storeProp][actionData.videoType] = actionData.dimensions; + this.setStoreState(nextState); } }); diff --git a/browser/components/loop/content/shared/js/mixins.js b/browser/components/loop/content/shared/js/mixins.js index 93dc79e425d..6595193e41d 100644 --- a/browser/components/loop/content/shared/js/mixins.js +++ b/browser/components/loop/content/shared/js/mixins.js @@ -157,29 +157,217 @@ loop.shared.mixins = (function() { * elements and handling updates of the media containers. */ var MediaSetupMixin = { + _videoDimensionsCache: { + local: {}, + remote: {} + }, + componentDidMount: function() { - rootObject.addEventListener('orientationchange', this.updateVideoContainer); - rootObject.addEventListener('resize', this.updateVideoContainer); + rootObject.addEventListener("orientationchange", this.updateVideoContainer); + rootObject.addEventListener("resize", this.updateVideoContainer); }, componentWillUnmount: function() { - rootObject.removeEventListener('orientationchange', this.updateVideoContainer); - rootObject.removeEventListener('resize', this.updateVideoContainer); + rootObject.removeEventListener("orientationchange", this.updateVideoContainer); + rootObject.removeEventListener("resize", this.updateVideoContainer); + }, + + /** + * Whenever the dimensions change of a video stream, this function is called + * by `updateVideoDimensions` to store the new values and notifies the callee + * if the dimensions changed compared to the currently stored values. + * + * @param {String} which Type of video stream. May be 'local' or 'remote' + * @param {Object} newDimensions Object containing 'width' and 'height' properties + * @return {Boolean} `true` when the dimensions have changed, + * `false` if not + */ + _updateDimensionsCache: function(which, newDimensions) { + var cache = this._videoDimensionsCache[which]; + var cacheKeys = Object.keys(cache); + var changed = false; + Object.keys(newDimensions).forEach(function(videoType) { + if (cacheKeys.indexOf(videoType) === -1) { + cache[videoType] = newDimensions[videoType]; + cache[videoType].aspectRatio = this.getAspectRatio(cache[videoType]); + changed = true; + return; + } + if (cache[videoType].width !== newDimensions[videoType].width) { + cache[videoType].width = newDimensions[videoType].width; + changed = true; + } + if (cache[videoType].height !== newDimensions[videoType].height) { + cache[videoType].height = newDimensions[videoType].height; + changed = true; + } + if (changed) { + cache[videoType].aspectRatio = this.getAspectRatio(cache[videoType]); + } + }, this); + return changed; + }, + + /** + * Whenever the dimensions change of a video stream, this function is called + * to process these changes and possibly trigger an update to the video + * container elements. + * + * @param {Object} localVideoDimensions Object containing 'width' and 'height' + * properties grouped by stream name + * @param {Object} remoteVideoDimensions Object containing 'width' and 'height' + * properties grouped by stream name + */ + updateVideoDimensions: function(localVideoDimensions, remoteVideoDimensions) { + var localChanged = this._updateDimensionsCache("local", localVideoDimensions); + var remoteChanged = this._updateDimensionsCache("remote", remoteVideoDimensions); + if (localChanged || remoteChanged) { + this.updateVideoContainer(); + } + }, + + /** + * Get the aspect ratio of a width/ height pair, which should be the dimensions + * of a stream. The returned object is an aspect ratio indexed by 1; the leading + * size has a value smaller than 1 and the slave size has a value of 1. + * this is exactly the same as notations like 4:3 and 16:9, which are merely + * human-readable forms of their fractional counterparts. 4:3 === 1:0.75 and + * 16:9 === 1:0.5625. + * So we're using the aspect ratios in their original form, because that's + * easier to do calculus with. + * + * Example: + * A stream with dimensions `{ width: 640, height: 480 }` yields an indexed + * aspect ratio of `{ width: 1, height: 0.75 }`. This means that the 'height' + * will determine the value of 'width' when the stream is stretched or shrunk + * to fit inside its container element at the maximum size. + * + * @param {Object} dimensions Object containing 'width' and 'height' properties + * @return {Object} Contains the indexed aspect ratio for 'width' + * and 'height' assigned to the corresponding + * properties. + */ + getAspectRatio: function(dimensions) { + if (dimensions.width === dimensions.height) { + return {width: 1, height: 1}; + } + var denominator = Math.max(dimensions.width, dimensions.height); + return { + width: dimensions.width / denominator, + height: dimensions.height / denominator + }; + }, + + /** + * Retrieve the dimensions of the remote video stream. + * Example output: + * { + * width: 680, + * height: 480, + * streamWidth: 640, + * streamHeight: 480, + * offsetX: 20, + * offsetY: 0 + * } + * + * Note: Once we support multiple remote video streams, this function will + * need to be updated. + * @return {Object} contains the remote stream dimension properties of its + * container node, the stream itself and offset of the stream + * relative to its container node in pixels. + */ + getRemoteVideoDimensions: function() { + var remoteVideoDimensions; + + Object.keys(this._videoDimensionsCache.remote).forEach(function(videoType) { + var node = this._getElement("." + (videoType === "camera" ? "remote" : videoType)); + var width = node.offsetWidth; + // If the width > 0 then we record its real size by taking its aspect + // ratio in account. Due to the 'contain' fit-mode, the stream will be + // centered inside the video element. + // We'll need to deal with more than one remote video stream whenever + // that becomes something we need to support. + if (width) { + remoteVideoDimensions = { + width: width, + height: node.offsetHeight + }; + var ratio = this._videoDimensionsCache.remote[videoType].aspectRatio; + var leadingAxis = Math.min(ratio.width, ratio.height) === ratio.width ? + "width" : "height"; + var slaveSize = remoteVideoDimensions[leadingAxis] + + (remoteVideoDimensions[leadingAxis] * (1 - ratio[leadingAxis])); + remoteVideoDimensions.streamWidth = leadingAxis === "width" ? + remoteVideoDimensions.width : slaveSize; + remoteVideoDimensions.streamHeight = leadingAxis === "height" ? + remoteVideoDimensions.height: slaveSize; + } + }, this); + + // Supply some sensible defaults for the remoteVideoDimensions if no remote + // stream is connected (yet). + if (!remoteVideoDimensions) { + var node = this._getElement(".remote"); + var width = node.offsetWidth; + var height = node.offsetHeight; + remoteVideoDimensions = { + width: width, + height: height, + streamWidth: width, + streamHeight: height + }; + } + + // Calculate the size of each individual letter- or pillarbox for convenience. + remoteVideoDimensions.offsetX = remoteVideoDimensions.width - + remoteVideoDimensions.streamWidth + if (remoteVideoDimensions.offsetX > 0) { + remoteVideoDimensions.offsetX /= 2; + } + remoteVideoDimensions.offsetY = remoteVideoDimensions.height - + remoteVideoDimensions.streamHeight; + if (remoteVideoDimensions.offsetY > 0) { + remoteVideoDimensions.offsetY /= 2; + } + + return remoteVideoDimensions; }, /** * Used to update the video container whenever the orientation or size of the * display area changes. + * + * Buffer the calls to this function to make sure we don't overflow the stack + * with update calls when many 'resize' event are fired, to prevent blocking + * the event loop. */ updateVideoContainer: function() { - var localStreamParent = this._getElement('.local .OT_publisher'); - var remoteStreamParent = this._getElement('.remote .OT_subscriber'); - if (localStreamParent) { - localStreamParent.style.width = "100%"; - } - if (remoteStreamParent) { - remoteStreamParent.style.height = "100%"; + if (this._bufferedUpdateVideo) { + rootObject.clearTimeout(this._bufferedUpdateVideo); + this._bufferedUpdateVideo = null; } + + this._bufferedUpdateVideo = rootObject.setTimeout(function() { + this._bufferedUpdateVideo = null; + var localStreamParent = this._getElement(".local .OT_publisher"); + var remoteStreamParent = this._getElement(".remote .OT_subscriber"); + if (localStreamParent) { + localStreamParent.style.width = "100%"; + } + if (remoteStreamParent) { + remoteStreamParent.style.height = "100%"; + } + + // Update the position and dimensions of the containers of local video + // streams, if necessary. The consumer of this mixin should implement the + // actual updating mechanism. + Object.keys(this._videoDimensionsCache.local).forEach(function(videoType) { + var ratio = this._videoDimensionsCache.local[videoType].aspectRatio + if (videoType == "camera" && this.updateLocalCameraPosition) { + this.updateLocalCameraPosition(ratio); + } + }, this); + }.bind(this), 0); }, /** diff --git a/browser/components/loop/content/shared/js/otSdkDriver.js b/browser/components/loop/content/shared/js/otSdkDriver.js index baf439dcc27..05d31dcf0e2 100644 --- a/browser/components/loop/content/shared/js/otSdkDriver.js +++ b/browser/components/loop/content/shared/js/otSdkDriver.js @@ -9,6 +9,7 @@ loop.OTSdkDriver = (function() { var sharedActions = loop.shared.actions; var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS; + var STREAM_PROPERTIES = loop.shared.utils.STREAM_PROPERTIES; /** * This is a wrapper for the OT sdk. It is used to translate the SDK events into @@ -51,6 +52,7 @@ loop.OTSdkDriver = (function() { // the media. this.publisher = this.sdk.initPublisher(this.getLocalElement(), this.publisherConfig); + this.publisher.on("streamCreated", this._onLocalStreamCreated.bind(this)); this.publisher.on("accessAllowed", this._onPublishComplete.bind(this)); this.publisher.on("accessDenied", this._onPublishDenied.bind(this)); this.publisher.on("accessDialogOpened", @@ -91,6 +93,7 @@ loop.OTSdkDriver = (function() { this._onConnectionDestroyed.bind(this)); this.session.on("sessionDisconnected", this._onSessionDisconnected.bind(this)); + this.session.on("streamPropertyChanged", this._onStreamPropertyChanged.bind(this)); // This starts the actual session connection. this.session.connect(sessionData.apiKey, sessionData.sessionToken, @@ -102,12 +105,13 @@ loop.OTSdkDriver = (function() { */ disconnectSession: function() { if (this.session) { - this.session.off("streamCreated connectionDestroyed sessionDisconnected"); + this.session.off("streamCreated streamDestroyed connectionDestroyed " + + "sessionDisconnected streamPropertyChanged"); this.session.disconnect(); delete this.session; } if (this.publisher) { - this.publisher.off("accessAllowed accessDenied accessDialogOpened"); + this.publisher.off("accessAllowed accessDenied accessDialogOpened streamCreated"); this.publisher.destroy(); delete this.publisher; } @@ -234,6 +238,14 @@ loop.OTSdkDriver = (function() { * https://tokbox.com/opentok/libraries/client/js/reference/StreamEvent.html */ _onRemoteStreamCreated: function(event) { + if (event.stream[STREAM_PROPERTIES.HAS_VIDEO]) { + this.dispatcher.dispatch(new sharedActions.VideoDimensionsChanged({ + isLocal: false, + videoType: event.stream.videoType, + dimensions: event.stream[STREAM_PROPERTIES.VIDEO_DIMENSIONS] + })); + } + this.session.subscribe(event.stream, this.getRemoteElement(), this.publisherConfig); @@ -243,6 +255,22 @@ loop.OTSdkDriver = (function() { } }, + /** + * Handles the event when the local stream is created. + * + * @param {StreamEvent} event The event details: + * https://tokbox.com/opentok/libraries/client/js/reference/StreamEvent.html + */ + _onLocalStreamCreated: function(event) { + if (event.stream[STREAM_PROPERTIES.HAS_VIDEO]) { + this.dispatcher.dispatch(new sharedActions.VideoDimensionsChanged({ + isLocal: true, + videoType: event.stream.videoType, + dimensions: event.stream[STREAM_PROPERTIES.VIDEO_DIMENSIONS] + })); + } + }, + /** * Called from the sdk when the media access dialog is opened. * Prevents the default action, to prevent the SDK's "allow access" @@ -282,6 +310,19 @@ loop.OTSdkDriver = (function() { })); }, + /** + * Handles publishing of property changes to a stream. + */ + _onStreamPropertyChanged: function(event) { + if (event.changedProperty == STREAM_PROPERTIES.VIDEO_DIMENSIONS) { + this.dispatcher.dispatch(new sharedActions.VideoDimensionsChanged({ + isLocal: event.stream.connection.id == this.session.connection.id, + videoType: event.stream.videoType, + dimensions: event.stream[STREAM_PROPERTIES.VIDEO_DIMENSIONS] + })); + } + }, + /** * Publishes the local stream if the session is connected * and the publisher is ready. diff --git a/browser/components/loop/content/shared/js/utils.js b/browser/components/loop/content/shared/js/utils.js index 25fe69d0a74..9906610af4b 100644 --- a/browser/components/loop/content/shared/js/utils.js +++ b/browser/components/loop/content/shared/js/utils.js @@ -42,6 +42,12 @@ loop.shared.utils = (function(mozL10n) { UNKNOWN: "reason-unknown" }; + var STREAM_PROPERTIES = { + VIDEO_DIMENSIONS: "videoDimensions", + HAS_AUDIO: "hasAudio", + HAS_VIDEO: "hasVideo" + }; + /** * Format a given date into an l10n-friendly string. * @@ -138,6 +144,7 @@ loop.shared.utils = (function(mozL10n) { FAILURE_DETAILS: FAILURE_DETAILS, REST_ERRNOS: REST_ERRNOS, WEBSOCKET_REASONS: WEBSOCKET_REASONS, + STREAM_PROPERTIES: STREAM_PROPERTIES, Helper: Helper, composeCallUrlEmail: composeCallUrlEmail, formatDate: formatDate, diff --git a/browser/components/loop/standalone/content/js/standaloneRoomViews.js b/browser/components/loop/standalone/content/js/standaloneRoomViews.js index 9a5642baec9..54781e191e4 100644 --- a/browser/components/loop/standalone/content/js/standaloneRoomViews.js +++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.js @@ -224,7 +224,9 @@ loop.standaloneRoomViews = (function(mozL10n) { * @private */ _onActiveRoomStateChanged: function() { - this.setState(this.props.activeRoomStore.getStoreState()); + var state = this.props.activeRoomStore.getStoreState(); + this.updateVideoDimensions(state.localVideoDimensions, state.remoteVideoDimensions); + this.setState(state); }, componentDidMount: function() { @@ -283,6 +285,41 @@ loop.standaloneRoomViews = (function(mozL10n) { })); }, + /** + * Specifically updates the local camera stream size and position, depending + * on the size and position of the remote video stream. + * This method gets called from `updateVideoContainer`, which is defined in + * the `MediaSetupMixin`. + * + * @param {Object} ratio Aspect ratio of the local camera stream + */ + updateLocalCameraPosition: function(ratio) { + var node = this._getElement(".local"); + var parent = node.offsetParent || this._getElement(".media"); + // The local camera view should be a sixth of the size of its offset parent + // and positioned to overlap with the remote stream at a quarter of its width. + var parentWidth = parent.offsetWidth; + var targetWidth = parentWidth / 6; + + node.style.right = "auto"; + if (window.matchMedia && window.matchMedia("screen and (max-width:640px)").matches) { + targetWidth = 180; + node.style.left = "auto"; + } else { + // Now position the local camera view correctly with respect to the remote + // video stream. + var remoteVideoDimensions = this.getRemoteVideoDimensions(); + var offsetX = (remoteVideoDimensions.streamWidth + remoteVideoDimensions.offsetX); + // The horizontal offset of the stream, and the width of the resulting + // pillarbox, is determined by the height exponent of the aspect ratio. + // Therefore we multiply the width of the local camera view by the height + // ratio. + node.style.left = (offsetX - ((targetWidth * ratio.height) / 4)) + "px"; + } + node.style.width = (targetWidth * ratio.width) + "px"; + node.style.height = (targetWidth * ratio.height) + "px"; + }, + /** * Checks if current room is active. * diff --git a/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx b/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx index a5b0ba367ab..4aca9aba4f6 100644 --- a/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx +++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx @@ -224,7 +224,9 @@ loop.standaloneRoomViews = (function(mozL10n) { * @private */ _onActiveRoomStateChanged: function() { - this.setState(this.props.activeRoomStore.getStoreState()); + var state = this.props.activeRoomStore.getStoreState(); + this.updateVideoDimensions(state.localVideoDimensions, state.remoteVideoDimensions); + this.setState(state); }, componentDidMount: function() { @@ -283,6 +285,41 @@ loop.standaloneRoomViews = (function(mozL10n) { })); }, + /** + * Specifically updates the local camera stream size and position, depending + * on the size and position of the remote video stream. + * This method gets called from `updateVideoContainer`, which is defined in + * the `MediaSetupMixin`. + * + * @param {Object} ratio Aspect ratio of the local camera stream + */ + updateLocalCameraPosition: function(ratio) { + var node = this._getElement(".local"); + var parent = node.offsetParent || this._getElement(".media"); + // The local camera view should be a sixth of the size of its offset parent + // and positioned to overlap with the remote stream at a quarter of its width. + var parentWidth = parent.offsetWidth; + var targetWidth = parentWidth / 6; + + node.style.right = "auto"; + if (window.matchMedia && window.matchMedia("screen and (max-width:640px)").matches) { + targetWidth = 180; + node.style.left = "auto"; + } else { + // Now position the local camera view correctly with respect to the remote + // video stream. + var remoteVideoDimensions = this.getRemoteVideoDimensions(); + var offsetX = (remoteVideoDimensions.streamWidth + remoteVideoDimensions.offsetX); + // The horizontal offset of the stream, and the width of the resulting + // pillarbox, is determined by the height exponent of the aspect ratio. + // Therefore we multiply the width of the local camera view by the height + // ratio. + node.style.left = (offsetX - ((targetWidth * ratio.height) / 4)) + "px"; + } + node.style.width = (targetWidth * ratio.width) + "px"; + node.style.height = (targetWidth * ratio.height) + "px"; + }, + /** * Checks if current room is active. * From 37eab9ecf62f2cb08c2e868ec730443161968bf0 Mon Sep 17 00:00:00 2001 From: Mike de Boer Date: Fri, 30 Jan 2015 16:01:42 +0000 Subject: [PATCH 003/101] Bug 1093780 Part 3 - add tests for contain mode functionality in the MediaSetup mixin. r=Standard8 --- .../loop/test/desktop-local/roomViews_test.js | 4 +- .../loop/test/shared/activeRoomStore_test.js | 25 +++++++ .../loop/test/shared/mixins_test.js | 71 +++++++++++++++++++ .../loop/test/shared/otSdkDriver_test.js | 39 ++++++++++ 4 files changed, 138 insertions(+), 1 deletion(-) diff --git a/browser/components/loop/test/desktop-local/roomViews_test.js b/browser/components/loop/test/desktop-local/roomViews_test.js index 200c6f45a2e..1bc48f5e808 100644 --- a/browser/components/loop/test/desktop-local/roomViews_test.js +++ b/browser/components/loop/test/desktop-local/roomViews_test.js @@ -68,7 +68,9 @@ describe("loop.roomViews", function () { videoMuted: false, failureReason: undefined, used: false, - foo: "bar" + foo: "bar", + localVideoDimensions: {}, + remoteVideoDimensions: {} }); }); diff --git a/browser/components/loop/test/shared/activeRoomStore_test.js b/browser/components/loop/test/shared/activeRoomStore_test.js index 8a9ffc65fcb..3fa25151f54 100644 --- a/browser/components/loop/test/shared/activeRoomStore_test.js +++ b/browser/components/loop/test/shared/activeRoomStore_test.js @@ -282,6 +282,31 @@ describe("loop.store.ActiveRoomStore", function () { }); }); + describe("#videoDimensionsChanged", function() { + it("should not contain any video dimensions at the very start", function() { + expect(store.getStoreState()).eql(store.getInitialStoreState()); + }); + + it("should update the store with new video dimensions", function() { + var actionData = { + isLocal: true, + videoType: "camera", + dimensions: { width: 640, height: 480 } + }; + + store.videoDimensionsChanged(new sharedActions.VideoDimensionsChanged(actionData)); + + expect(store.getStoreState().localVideoDimensions) + .to.have.property(actionData.videoType, actionData.dimensions); + + actionData.isLocal = false; + store.videoDimensionsChanged(new sharedActions.VideoDimensionsChanged(actionData)); + + expect(store.getStoreState().remoteVideoDimensions) + .to.have.property(actionData.videoType, actionData.dimensions); + }); + }); + describe("#setupRoomInfo", function() { var fakeRoomInfo; diff --git a/browser/components/loop/test/shared/mixins_test.js b/browser/components/loop/test/shared/mixins_test.js index 7c6dbcc6658..bdbdd12f4ab 100644 --- a/browser/components/loop/test/shared/mixins_test.js +++ b/browser/components/loop/test/shared/mixins_test.js @@ -204,8 +204,16 @@ describe("loop.shared.mixins", function() { } }); + sandbox.useFakeTimers(); + rootObject = { events: {}, + setTimeout: function(func, timeout) { + return setTimeout(func, timeout); + }, + clearTimeout: function(timer) { + return clearTimeout(timer); + }, addEventListener: function(eventName, listener) { this.events[eventName] = listener; }, @@ -244,20 +252,26 @@ describe("loop.shared.mixins", function() { describe("resize", function() { it("should update the width on the local stream element", function() { localElement = { + offsetWidth: 100, + offsetHeight: 100, style: { width: "0%" } }; rootObject.events.resize(); + sandbox.clock.tick(10); expect(localElement.style.width).eql("100%"); }); it("should update the height on the remote stream element", function() { remoteElement = { + offsetWidth: 100, + offsetHeight: 100, style: { height: "0%" } }; rootObject.events.resize(); + sandbox.clock.tick(10); expect(remoteElement.style.height).eql("100%"); }); @@ -266,24 +280,81 @@ describe("loop.shared.mixins", function() { describe("orientationchange", function() { it("should update the width on the local stream element", function() { localElement = { + offsetWidth: 100, + offsetHeight: 100, style: { width: "0%" } }; rootObject.events.orientationchange(); + sandbox.clock.tick(10); expect(localElement.style.width).eql("100%"); }); it("should update the height on the remote stream element", function() { remoteElement = { + offsetWidth: 100, + offsetHeight: 100, style: { height: "0%" } }; rootObject.events.orientationchange(); + sandbox.clock.tick(10); expect(remoteElement.style.height).eql("100%"); }); }); + + + describe("Video stream dimensions", function() { + var localVideoDimensions = { + camera: { + width: 640, + height: 480 + } + }; + var remoteVideoDimensions = { + camera: { + width: 420, + height: 138 + } + }; + + beforeEach(function() { + view.updateVideoDimensions(localVideoDimensions, remoteVideoDimensions); + }); + + it("should register video dimension updates correctly", function() { + expect(view._videoDimensionsCache.local.camera.width) + .eql(localVideoDimensions.camera.width); + expect(view._videoDimensionsCache.local.camera.height) + .eql(localVideoDimensions.camera.height); + expect(view._videoDimensionsCache.local.camera.aspectRatio.width).eql(1); + expect(view._videoDimensionsCache.local.camera.aspectRatio.height).eql(0.75); + expect(view._videoDimensionsCache.remote.camera.width) + .eql(remoteVideoDimensions.camera.width); + expect(view._videoDimensionsCache.remote.camera.height) + .eql(remoteVideoDimensions.camera.height); + expect(view._videoDimensionsCache.remote.camera.aspectRatio.width).eql(1); + expect(view._videoDimensionsCache.remote.camera.aspectRatio.height) + .eql(0.32857142857142857); + }); + + it("should fetch remote video stream dimensions correctly", function() { + remoteElement = { + offsetWidth: 600, + offsetHeight: 320 + }; + + var remoteVideoDimensions = view.getRemoteVideoDimensions(); + expect(remoteVideoDimensions.width).eql(remoteElement.offsetWidth); + expect(remoteVideoDimensions.height).eql(remoteElement.offsetHeight); + expect(remoteVideoDimensions.streamWidth).eql(534.8571428571429); + expect(remoteVideoDimensions.streamHeight).eql(remoteElement.offsetHeight); + expect(remoteVideoDimensions.offsetX).eql(32.571428571428555); + expect(remoteVideoDimensions.offsetY).eql(0); + }); + }); }); }); diff --git a/browser/components/loop/test/shared/otSdkDriver_test.js b/browser/components/loop/test/shared/otSdkDriver_test.js index 613e8db9f21..b058af9a660 100644 --- a/browser/components/loop/test/shared/otSdkDriver_test.js +++ b/browser/components/loop/test/shared/otSdkDriver_test.js @@ -8,6 +8,7 @@ describe("loop.OTSdkDriver", function () { var sharedActions = loop.shared.actions; var FAILURE_DETAILS = loop.shared.utils.FAILURE_DETAILS; + var STREAM_PROPERTIES = loop.shared.utils.STREAM_PROPERTIES; var sandbox; var dispatcher, driver, publisher, sdk, session, sessionData; var fakeLocalElement, fakeRemoteElement, publisherConfig, fakeEvent; @@ -310,6 +311,44 @@ describe("loop.OTSdkDriver", function () { }); }); + describe("streamPropertyChanged", function() { + var fakeStream = { + connection: { id: "fake" }, + videoType: "screen", + videoDimensions: { + width: 320, + height: 160 + } + }; + + it("should not dispatch a VideoDimensionsChanged action for other properties", function() { + session.trigger("streamPropertyChanged", { + stream: fakeStream, + changedProperty: STREAM_PROPERTIES.HAS_AUDIO + }); + session.trigger("streamPropertyChanged", { + stream: fakeStream, + changedProperty: STREAM_PROPERTIES.HAS_VIDEO + }); + + sinon.assert.notCalled(dispatcher.dispatch); + }); + + it("should dispatch a VideoDimensionsChanged action", function() { + session.connection = { + id: "localUser" + }; + session.trigger("streamPropertyChanged", { + stream: fakeStream, + changedProperty: STREAM_PROPERTIES.VIDEO_DIMENSIONS + }); + + sinon.assert.calledOnce(dispatcher.dispatch); + sinon.assert.calledWithMatch(dispatcher.dispatch, + sinon.match.hasOwn("name", "videoDimensionsChanged")) + }) + }); + describe("connectionCreated", function() { beforeEach(function() { session.connection = { From 43075c4fa522ce5f660b25af4dc40aea734a0fa8 Mon Sep 17 00:00:00 2001 From: Mark Banner Date: Fri, 30 Jan 2015 16:01:42 +0000 Subject: [PATCH 004/101] Bug 1093780 Part 4 - Fix the audio-only display of avatars for the new sdk. r=mikedeboer --- .../loop/content/shared/css/conversation.css | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/browser/components/loop/content/shared/css/conversation.css b/browser/components/loop/content/shared/css/conversation.css index fd2007483c2..620d3fed34b 100644 --- a/browser/components/loop/content/shared/css/conversation.css +++ b/browser/components/loop/content/shared/css/conversation.css @@ -503,10 +503,11 @@ * XXX this approach is fragile because it makes assumptions * about the generated OT markup, any change will break it */ -.local-stream.local-stream-audio, -.standalone .OT_subscriber .OT_video-poster, -.fx-embedded .OT_subscriber .OT_video-poster, -.local-stream-audio .OT_publisher .OT_video-poster { + +/* + * For any audio-only streams, we want to display our own background + */ +.OT_audio-only .OT_widget-container .OT_video-poster { background-image: url("../img/audio-call-avatar.svg"); background-repeat: no-repeat; background-color: #4BA6E7; @@ -514,6 +515,22 @@ background-position: center; } +/* + * Audio-only. For local streams, cancel out the SDK's opacity of 0.25. + * For remote streams we leave them shaded, as otherwise its too bright. + */ +.local-stream-audio .OT_publisher .OT_video-poster { + opacity: 1 +} + +/* + * In audio-only mode, don't display the video element, doing so interferes + * with the background opacity of the video-poster element. + */ +.OT_audio-only .OT_widget-container .OT_video-element { + display: none; +} + /* * Ensure that the publisher (i.e. local) video is never cropped, so that it's * not possible for someone to be presented with a picture that displays From 56513970b3e523d3593af80bd22f9f1981d653dd Mon Sep 17 00:00:00 2001 From: Brian Grinstead Date: Fri, 30 Jan 2015 08:16:24 -0800 Subject: [PATCH 005/101] Bug 1127351 - Override background-image on findbar in DevEdition to fix dark theme styling;r=Gijs --- browser/themes/shared/devedition.inc.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/browser/themes/shared/devedition.inc.css b/browser/themes/shared/devedition.inc.css index c6e9de00d05..c511f2a97b7 100644 --- a/browser/themes/shared/devedition.inc.css +++ b/browser/themes/shared/devedition.inc.css @@ -182,6 +182,10 @@ color: var(--chrome-color); } +.browserContainer > findbar { + background-image: none; +} + #navigator-toolbox .toolbarbutton-1, .browserContainer > findbar .findbar-button, #PlacesToolbar toolbarbutton.bookmark-item { From feb66be0d15d858ad2349b3088a09555e52e9107 Mon Sep 17 00:00:00 2001 From: Brian Grinstead Date: Fri, 30 Jan 2015 08:18:36 -0800 Subject: [PATCH 006/101] Bug 1125677 - Update find bar styling in DevEdition theme;r=Gijs --- browser/themes/shared/devedition.inc.css | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/browser/themes/shared/devedition.inc.css b/browser/themes/shared/devedition.inc.css index c511f2a97b7..4d6610b39a0 100644 --- a/browser/themes/shared/devedition.inc.css +++ b/browser/themes/shared/devedition.inc.css @@ -157,7 +157,8 @@ /* End override @tabCurveHalfWidth@ and @tabCurveWidth@ */ #urlbar ::-moz-selection, -#navigator-toolbox .searchbar-textbox ::-moz-selection { +#navigator-toolbox .searchbar-textbox ::-moz-selection, +.browserContainer > findbar ::-moz-selection { background-color: var(--chrome-selection-background-color); color: var(--chrome-selection-color); } @@ -186,6 +187,12 @@ background-image: none; } +/* Default findbar text color doesn't look good - Bug 1125677 */ +.browserContainer > findbar .findbar-find-status, +.browserContainer > findbar .found-matches { + color: inherit; +} + #navigator-toolbox .toolbarbutton-1, .browserContainer > findbar .findbar-button, #PlacesToolbar toolbarbutton.bookmark-item { From 9ddcb19caebb042816a43ef3ec6883614ddc4da1 Mon Sep 17 00:00:00 2001 From: Dave Townsend Date: Tue, 13 Jan 2015 12:33:26 -0800 Subject: [PATCH 007/101] Bug 1112304: Update about:tabcrashed to match the new UX spec. r=dao Uses in-content styles for the tab crash page and adds an overlay to the favicon for crashed tabs. Adds support for closing the crashed tab. The strings here also refer to being able to restore all tabs, that will be implemented and landed at the same time in bug 1109650 to avoid l10n churn. --- browser/base/content/aboutTabCrashed.css | 8 ++ browser/base/content/aboutTabCrashed.js | 36 +++--- browser/base/content/aboutTabCrashed.xhtml | 20 ++-- browser/base/content/browser.js | 12 +- browser/base/content/tabbrowser.css | 5 +- browser/base/content/tabbrowser.xml | 12 +- browser/base/jar.mn | 1 + .../sessionstore/test/browser_crashedTabs.js | 75 +++++++++++- browser/components/sessionstore/test/head.js | 14 ++- .../en-US/chrome/browser/aboutTabCrashed.dtd | 10 ++ .../locales/en-US/chrome/browser/browser.dtd | 5 - browser/locales/jar.mn | 1 + browser/modules/TabCrashReporter.jsm | 1 + browser/themes/linux/aboutTabCrashed.css | 108 ------------------ browser/themes/linux/jar.mn | 1 + browser/themes/osx/browser.css | 8 ++ browser/themes/osx/jar.mn | 1 + browser/themes/shared/aboutTabCrashed.css | 12 +- browser/themes/shared/tabbrowser/crashed.svg | 16 +++ browser/themes/shared/tabs.inc.css | 23 +++- browser/themes/windows/jar.mn | 2 + .../themes/linux/global/in-content/common.css | 1 + .../themes/osx/global/in-content/common.css | 4 +- .../themes/shared/in-content/common.inc.css | 35 +++++- .../windows/global/in-content/common.css | 1 + 25 files changed, 250 insertions(+), 162 deletions(-) create mode 100644 browser/base/content/aboutTabCrashed.css create mode 100644 browser/locales/en-US/chrome/browser/aboutTabCrashed.dtd delete mode 100644 browser/themes/linux/aboutTabCrashed.css create mode 100644 browser/themes/shared/tabbrowser/crashed.svg diff --git a/browser/base/content/aboutTabCrashed.css b/browser/base/content/aboutTabCrashed.css new file mode 100644 index 00000000000..4122506da9d --- /dev/null +++ b/browser/base/content/aboutTabCrashed.css @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +html:not(.crashDumpSubmitted) #reportSent, +html:not(.crashDumpAvailable) #report-box { + display: none; +} diff --git a/browser/base/content/aboutTabCrashed.js b/browser/base/content/aboutTabCrashed.js index d12a6c16afb..989aa8d1468 100644 --- a/browser/base/content/aboutTabCrashed.js +++ b/browser/base/content/aboutTabCrashed.js @@ -12,21 +12,31 @@ function parseQueryString() { document.title = parseQueryString(); -addEventListener("DOMContentLoaded", () => { - let tryAgain = document.getElementById("tryAgain"); - let sendCrashReport = document.getElementById("checkSendReport"); +function shouldSendReport() { + if (!document.documentElement.classList.contains("crashDumpAvailable")) + return false; + return document.getElementById("sendReport").checked; +} - tryAgain.addEventListener("click", () => { - let event = new CustomEvent("AboutTabCrashedTryAgain", { - bubbles: true, - detail: { - sendCrashReport: sendCrashReport.checked, - }, - }); - - document.dispatchEvent(event); +function sendEvent(message) { + let event = new CustomEvent("AboutTabCrashedMessage", { + bubbles: true, + detail: { + message, + sendCrashReport: shouldSendReport(), + }, }); -}); + + document.dispatchEvent(event); +} + +function closeTab() { + sendEvent("closeTab"); +} + +function restoreTab() { + sendEvent("restoreTab"); +} // Error pages are loaded as LOAD_BACKGROUND, so they don't get load events. var event = new CustomEvent("AboutTabCrashedLoad", {bubbles:true}); diff --git a/browser/base/content/aboutTabCrashed.xhtml b/browser/base/content/aboutTabCrashed.xhtml index d19429adff9..d15c662623b 100644 --- a/browser/base/content/aboutTabCrashed.xhtml +++ b/browser/base/content/aboutTabCrashed.xhtml @@ -12,18 +12,19 @@ %globalDTD; - - %browserDTD; %brandDTD; - + + %tabCrashedDTD; ]> + @@ -36,12 +37,17 @@

&tabCrashed.message;

- - + +
+

&tabCrashed.reportSent;

+
- + +
diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 8bf3e15c39c..113e80c834c 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -1116,7 +1116,7 @@ var gBrowserInit = { #endif }, false, true); - gBrowser.addEventListener("AboutTabCrashedTryAgain", function(event) { + gBrowser.addEventListener("AboutTabCrashedMessage", function(event) { let ownerDoc = event.originalTarget; if (!ownerDoc.documentURI.startsWith("about:tabcrashed")) { @@ -1134,8 +1134,16 @@ var gBrowserInit = { TabCrashReporter.submitCrashReport(browser); } #endif + let tab = gBrowser.getTabForBrowser(browser); - SessionStore.reviveCrashedTab(tab); + switch (event.detail.message) { + case "closeTab": + gBrowser.removeTab(tab, { animate: true }); + break; + case "restoreTab": + SessionStore.reviveCrashedTab(tab); + break; + } }, false, true); let uriToLoad = this._getUriToLoad(); diff --git a/browser/base/content/tabbrowser.css b/browser/base/content/tabbrowser.css index a9dc8391f2e..121a7ad4294 100644 --- a/browser/base/content/tabbrowser.css +++ b/browser/base/content/tabbrowser.css @@ -51,9 +51,10 @@ tabpanels { } } -.tab-icon-image:not([src]):not([pinned]), +.tab-icon-image:not([src]):not([pinned]):not([crashed]), .tab-throbber:not([busy]), -.tab-throbber[busy] + .tab-icon-image { +.tab-icon-image[busy], +.tab-icon-overlay[busy] { display: none; } diff --git a/browser/base/content/tabbrowser.xml b/browser/base/content/tabbrowser.xml index abb3d08e59f..a419c9929ad 100644 --- a/browser/base/content/tabbrowser.xml +++ b/browser/base/content/tabbrowser.xml @@ -664,9 +664,13 @@ // We need to add 2 because loadURIWithFlags may have // cancelled a pending load which would have cleared // its anchor scroll detection temporary increment. - if (aWebProgress.isTopLevel) + if (aWebProgress.isTopLevel) { this.mBrowser.userTypedClear += 2; + // If the browser is loading it must not be crashed anymore + this.mTab.removeAttribute("crashed"); + } + if (this._shouldShowProgress(aRequest)) { if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) { this.mTab.setAttribute("busy", "true"); @@ -3579,6 +3583,7 @@ browser.docShell.displayLoadError(Cr.NS_ERROR_CONTENT_CRASHED, uri, null); browser.removeAttribute("crashedPageTitle"); let tab = this.getTabForBrowser(browser); + tab.setAttribute("crashed", true); this.setIcon(tab, icon); ]]> @@ -4980,11 +4985,14 @@ class="tab-throbber" role="presentation" layer="true" /> - + { + Services.prefs.clearUserPref("browser.tabs.animate"); +}); + /** * Returns a Promise that resolves once a remote has experienced * a crash. Also does the job of cleaning up the minidump of the crash. @@ -58,6 +64,7 @@ function crashBrowser(browser) { } Services.obs.removeObserver(observer, 'ipc:content-shutdown'); + info("Crash cleaned up"); resolve(); }; @@ -67,6 +74,7 @@ function crashBrowser(browser) { let aboutTabCrashedLoadPromise = new Promise((resolve, reject) => { browser.addEventListener("AboutTabCrashedLoad", function onCrash() { browser.removeEventListener("AboutTabCrashedLoad", onCrash, false); + info("about:tabcrashed loaded"); resolve(); }, false, true); }); @@ -75,7 +83,22 @@ function crashBrowser(browser) { // evaluated. let mm = browser.messageManager; mm.loadFrameScript("data:,(" + frame_script.toString() + ")();", false); - return Promise.all([crashCleanupPromise, aboutTabCrashedLoadPromise]); + return Promise.all([crashCleanupPromise, aboutTabCrashedLoadPromise]).then(() => { + let tab = gBrowser.getTabForBrowser(browser); + is(tab.getAttribute("crashed"), "true", "Tab should be marked as crashed"); + }); +} + +function clickButton(browser, id) { + info("Clicking " + id); + + let frame_script = (id) => { + let button = content.document.getElementById(id); + button.click(); + }; + + let mm = browser.messageManager; + mm.loadFrameScript("data:,(" + frame_script.toString() + ")('" + id + "');", false); } /** @@ -232,6 +255,7 @@ add_task(function test_revived_history_from_remote() { // become remote again. browser.loadURI(PAGE_2); yield promiseTabRestored(newTab); + ok(!newTab.hasAttribute("crashed"), "Tab shouldn't be marked as crashed anymore."); ok(browser.isRemoteBrowser, "Should be a remote browser"); TabState.flush(browser); @@ -272,6 +296,7 @@ add_task(function test_revived_history_from_non_remote() { // become remote again. browser.loadURI("about:mozilla"); yield promiseBrowserLoaded(browser); + ok(!newTab.hasAttribute("crashed"), "Tab shouldn't be marked as crashed anymore."); ok(!browser.isRemoteBrowser, "Should not be a remote browser"); TabState.flush(browser); @@ -301,6 +326,14 @@ add_task(function test_revive_tab_from_session_store() { browser.loadURI(PAGE_1); yield promiseBrowserLoaded(browser); + let newTab2 = gBrowser.addTab(); + let browser2 = newTab2.linkedBrowser; + ok(browser2.isRemoteBrowser, "Should be a remote browser"); + yield promiseBrowserLoaded(browser2); + + browser.loadURI(PAGE_1); + yield promiseBrowserLoaded(browser); + browser.loadURI(PAGE_2); yield promiseBrowserLoaded(browser); @@ -308,12 +341,17 @@ add_task(function test_revive_tab_from_session_store() { // Crash the tab yield crashBrowser(browser); + + is(newTab2.getAttribute("crashed"), "true", "Second tab should be crashed too."); + // Flush out any notifications from the crashed browser. TabState.flush(browser); // Use SessionStore to revive the tab - SessionStore.reviveCrashedTab(newTab); + clickButton(browser, "restoreTab"); yield promiseBrowserLoaded(browser); + ok(!newTab.hasAttribute("crashed"), "Tab shouldn't be marked as crashed anymore."); + is(newTab2.getAttribute("crashed"), "true", "Second tab should still be crashed though."); // We can't just check browser.currentURI.spec, because from // the outside, a crashed tab has the same URI as the page @@ -325,4 +363,35 @@ add_task(function test_revive_tab_from_session_store() { yield promiseHistoryLength(browser, 2); gBrowser.removeTab(newTab); -}); \ No newline at end of file + gBrowser.removeTab(newTab2); +}); + + +/** + * Checks that about:tabcrashed can close the current tab + */ +add_task(function test_close_tab_after_crash() { + let newTab = gBrowser.addTab(); + gBrowser.selectedTab = newTab; + let browser = newTab.linkedBrowser; + ok(browser.isRemoteBrowser, "Should be a remote browser"); + yield promiseBrowserLoaded(browser); + + browser.loadURI(PAGE_1); + yield promiseBrowserLoaded(browser); + + TabState.flush(browser); + + // Crash the tab + yield crashBrowser(browser); + // Flush out any notifications from the crashed browser. + TabState.flush(browser); + + let promise = promiseEvent(gBrowser.tabContainer, "TabClose"); + + // Click the close tab button + clickButton(browser, "closeTab"); + yield promise; + + is(gBrowser.tabs.length, 1, "Should have closed the tab"); +}); diff --git a/browser/components/sessionstore/test/head.js b/browser/components/sessionstore/test/head.js index e71e6004bae..bd5f27a63dd 100644 --- a/browser/components/sessionstore/test/head.js +++ b/browser/components/sessionstore/test/head.js @@ -488,15 +488,19 @@ function promiseDelayedStartupFinished(aWindow) { return new Promise(resolve => whenDelayedStartupFinished(aWindow, resolve)); } -function promiseTabRestored(tab) { +function promiseEvent(element, eventType, isCapturing = false) { return new Promise(resolve => { - tab.addEventListener("SSTabRestored", function onRestored() { - tab.removeEventListener("SSTabRestored", onRestored); - resolve(); - }); + element.addEventListener(eventType, function listener(event) { + element.removeEventListener(eventType, listener, isCapturing); + resolve(event); + }, isCapturing); }); } +function promiseTabRestored(tab) { + return promiseEvent(tab, "SSTabRestored"); +} + function sendMessage(browser, name, data = {}) { browser.messageManager.sendAsyncMessage(name, data); return promiseContentMessage(browser, name); diff --git a/browser/locales/en-US/chrome/browser/aboutTabCrashed.dtd b/browser/locales/en-US/chrome/browser/aboutTabCrashed.dtd new file mode 100644 index 00000000000..fa1f02c4396 --- /dev/null +++ b/browser/locales/en-US/chrome/browser/aboutTabCrashed.dtd @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/browser/locales/en-US/chrome/browser/browser.dtd b/browser/locales/en-US/chrome/browser/browser.dtd index 0553b443f66..5eb959c4d21 100644 --- a/browser/locales/en-US/chrome/browser/browser.dtd +++ b/browser/locales/en-US/chrome/browser/browser.dtd @@ -795,11 +795,6 @@ just addresses the organization to follow, e.g. "This site is run by " --> a CSS length value. --> - - - - - diff --git a/browser/locales/jar.mn b/browser/locales/jar.mn index fc739544d9b..06c914a862c 100644 --- a/browser/locales/jar.mn +++ b/browser/locales/jar.mn @@ -18,6 +18,7 @@ locale/browser/aboutHealthReport.dtd (%chrome/browser/aboutHealthReport.dtd) #endif locale/browser/aboutSessionRestore.dtd (%chrome/browser/aboutSessionRestore.dtd) + locale/browser/aboutTabCrashed.dtd (%chrome/browser/aboutTabCrashed.dtd) #ifdef MOZ_SERVICES_SYNC locale/browser/syncProgress.dtd (%chrome/browser/syncProgress.dtd) locale/browser/syncCustomize.dtd (%chrome/browser/syncCustomize.dtd) diff --git a/browser/modules/TabCrashReporter.jsm b/browser/modules/TabCrashReporter.jsm index 677688c1465..030e9c96f32 100644 --- a/browser/modules/TabCrashReporter.jsm +++ b/browser/modules/TabCrashReporter.jsm @@ -82,6 +82,7 @@ this.TabCrashReporter = { if (this.browserMap.get(browser) == childID) { this.browserMap.delete(browser); browser.contentDocument.documentElement.classList.remove("crashDumpAvailable"); + browser.contentDocument.documentElement.classList.add("crashDumpSubmitted"); } } } diff --git a/browser/themes/linux/aboutTabCrashed.css b/browser/themes/linux/aboutTabCrashed.css deleted file mode 100644 index 54d6e8f7f4f..00000000000 --- a/browser/themes/linux/aboutTabCrashed.css +++ /dev/null @@ -1,108 +0,0 @@ -body { - background-color: rgb(241, 244, 248); - margin-top: 2em; - font: message-box; - font-size: 100%; -} - -p { - font-size: .8em; -} - -#error-box { - background: url('chrome://global/skin/icons/information-24.png') no-repeat left 4px; - -moz-padding-start: 30px; -} - -#error-box:-moz-locale-dir(rtl) { - background-position: right 4px; -} - -#main-error-msg { - color: #4b4b4b; - font-weight: bold; -} - -#report-box { - text-align: center; - width: 75%; - margin: 0 auto; - display: none; -} - -.crashDumpAvailable #report-box { - display: block -} - -#button-box { - text-align: center; - width: 75%; - margin: 0 auto; -} - -@media all and (min-width: 300px) { - #error-box { - max-width: 50%; - margin: 0 auto; - background-image: url('chrome://global/skin/icons/information-32.png'); - min-height: 36px; - -moz-padding-start: 38px; - } - - button { - width: auto !important; - min-width: 150px; - } -} - -@media all and (min-width: 780px) { - #error-box { - max-width: 30%; - } -} - -button { - font: message-box; - font-size: 0.6875em; - -moz-appearance: none; - -moz-user-select: none; - width: 100%; - margin: 2px 0; - padding: 2px 6px; - line-height: 1.2; - background-color: hsla(210,30%,95%,.1); - background-image: linear-gradient(hsla(0,0%,100%,.6), hsla(0,0%,100%,.1)); - background-clip: padding-box; - border: 1px solid hsla(210,15%,25%,.4); - border-color: hsla(210,15%,25%,.3) hsla(210,15%,25%,.35) hsla(210,15%,25%,.4); - border-radius: 3px; - box-shadow: 0 1px 0 hsla(0,0%,100%,.3) inset, - 0 0 0 1px hsla(0,0%,100%,.3) inset, - 0 1px 0 hsla(0,0%,100%,.1); - - transition-property: background-color, border-color, box-shadow; - transition-duration: 150ms; - transition-timing-function: ease; - -} - -button:hover { - background-color: hsla(210,30%,95%,.8); - border-color: hsla(210,15%,25%,.45) hsla(210,15%,25%,.5) hsla(210,15%,25%,.55); - box-shadow: 0 1px 0 hsla(0,0%,100%,.3) inset, - 0 0 0 1px hsla(0,0%,100%,.3) inset, - 0 1px 0 hsla(0,0%,100%,.1), - 0 0 3px hsla(210,15%,25%,.1); - transition-property: background-color, border-color, box-shadow; - transition-duration: 150ms; - transition-timing-function: ease; -} - -button:hover:active { - background-color: hsla(210,15%,25%,.2); - box-shadow: 0 1px 1px hsla(210,15%,25%,.2) inset, - 0 0 2px hsla(210,15%,25%,.4) inset; - transition-property: background-color, border-color, box-shadow; - transition-duration: 10ms; - transition-timing-function: linear; -} diff --git a/browser/themes/linux/jar.mn b/browser/themes/linux/jar.mn index a99dc4e786e..1bfa3112a83 100644 --- a/browser/themes/linux/jar.mn +++ b/browser/themes/linux/jar.mn @@ -191,6 +191,7 @@ browser.jar: skin/classic/browser/tabbrowser/alltabs.png (tabbrowser/alltabs.png) skin/classic/browser/tabbrowser/alltabs-inverted.png (tabbrowser/alltabs-inverted.png) skin/classic/browser/tabbrowser/connecting.png (tabbrowser/connecting.png) + skin/classic/browser/tabbrowser/crashed.svg (../shared/tabbrowser/crashed.svg) skin/classic/browser/tabbrowser/loading.png (tabbrowser/loading.png) skin/classic/browser/tabbrowser/tab-active-middle.png (tabbrowser/tab-active-middle.png) skin/classic/browser/tabbrowser/tab-arrow-left.png (tabbrowser/tab-arrow-left.png) diff --git a/browser/themes/osx/browser.css b/browser/themes/osx/browser.css index 38aed4bf54a..0312b384bb6 100644 --- a/browser/themes/osx/browser.css +++ b/browser/themes/osx/browser.css @@ -3092,6 +3092,14 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker { opacity: .9; } +/* + * Force the overlay to create a new stacking context so it always appears on + * top of the icon. + */ +.tab-icon-overlay { + opacity: 0.9999; +} + .tab-label:not([selected="true"]) { opacity: .7; } diff --git a/browser/themes/osx/jar.mn b/browser/themes/osx/jar.mn index 64bb99f4c3e..3437e71fbed 100644 --- a/browser/themes/osx/jar.mn +++ b/browser/themes/osx/jar.mn @@ -301,6 +301,7 @@ browser.jar: skin/classic/browser/tabbrowser/alltabs-box-bkgnd-icon.png (tabbrowser/alltabs-box-bkgnd-icon.png) skin/classic/browser/tabbrowser/alltabs-box-bkgnd-icon-inverted.png (tabbrowser/alltabs-box-bkgnd-icon-inverted.png) skin/classic/browser/tabbrowser/alltabs-box-bkgnd-icon-inverted@2x.png (tabbrowser/alltabs-box-bkgnd-icon-inverted@2x.png) + skin/classic/browser/tabbrowser/crashed.svg (../shared/tabbrowser/crashed.svg) skin/classic/browser/tabbrowser/newtab.png (tabbrowser/newtab.png) skin/classic/browser/tabbrowser/newtab@2x.png (tabbrowser/newtab@2x.png) skin/classic/browser/tabbrowser/newtab-inverted.png (tabbrowser/newtab-inverted.png) diff --git a/browser/themes/shared/aboutTabCrashed.css b/browser/themes/shared/aboutTabCrashed.css index 2ae76e112c5..2ef767eb8b2 100644 --- a/browser/themes/shared/aboutTabCrashed.css +++ b/browser/themes/shared/aboutTabCrashed.css @@ -1,11 +1,11 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + .title { background-image: url("chrome://browser/skin/tab-crashed.svg"); } -#report-box { - display: none; +#reportSent { + font-weight: bold; } - -.crashDumpAvailable #report-box { - display: block -} \ No newline at end of file diff --git a/browser/themes/shared/tabbrowser/crashed.svg b/browser/themes/shared/tabbrowser/crashed.svg new file mode 100644 index 00000000000..28a73750dbb --- /dev/null +++ b/browser/themes/shared/tabbrowser/crashed.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + diff --git a/browser/themes/shared/tabs.inc.css b/browser/themes/shared/tabs.inc.css index 2fd3f05aa10..11fd26df1cb 100644 --- a/browser/themes/shared/tabs.inc.css +++ b/browser/themes/shared/tabs.inc.css @@ -65,6 +65,10 @@ -moz-padding-start: 9px; } +.tab-content[pinned] { + -moz-padding-end: 3px; +} + .tab-throbber, .tab-icon-image, .tab-close-button { @@ -75,12 +79,26 @@ .tab-icon-image { height: 16px; width: 16px; + -moz-margin-end: 6px; } .tab-icon-image { list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png"); } +.tab-icon-overlay { + width: 16px; + height: 16px; + margin-top: 10px; + -moz-margin-start: -16px; + display: none; +} + +.tab-icon-overlay[crashed] { + display: -moz-box; + list-style-image: url("chrome://browser/skin/tabbrowser/crashed.svg"); +} + .tab-throbber[busy] { list-style-image: url("chrome://browser/skin/tabbrowser/connecting.png"); } @@ -89,11 +107,6 @@ list-style-image: url("chrome://browser/skin/tabbrowser/loading.png"); } -.tab-throbber:not([pinned]), -.tab-icon-image:not([pinned]) { - -moz-margin-end: 6px; -} - .tab-label { -moz-margin-end: 0; -moz-margin-start: 0; diff --git a/browser/themes/windows/jar.mn b/browser/themes/windows/jar.mn index e96e9c8f85d..8cd019284f3 100644 --- a/browser/themes/windows/jar.mn +++ b/browser/themes/windows/jar.mn @@ -219,6 +219,7 @@ browser.jar: skin/classic/browser/tabbrowser/newtab.png (tabbrowser/newtab-XPVista7.png) skin/classic/browser/tabbrowser/newtab-inverted.png (tabbrowser/newtab-inverted.png) skin/classic/browser/tabbrowser/connecting.png (tabbrowser/connecting.png) + skin/classic/browser/tabbrowser/crashed.svg (../shared/tabbrowser/crashed.svg) skin/classic/browser/tabbrowser/loading.png (tabbrowser/loading.png) skin/classic/browser/tabbrowser/tab-active-middle.png (tabbrowser/tab-active-middle.png) skin/classic/browser/tabbrowser/tab-active-middle@2x.png (tabbrowser/tab-active-middle@2x.png) @@ -689,6 +690,7 @@ browser.jar: skin/classic/aero/browser/tabbrowser/newtab-XPVista7.png (tabbrowser/newtab-XPVista7.png) skin/classic/aero/browser/tabbrowser/newtab-inverted.png (tabbrowser/newtab-inverted.png) skin/classic/aero/browser/tabbrowser/connecting.png (tabbrowser/connecting.png) + skin/classic/aero/browser/tabbrowser/crashed.svg (../shared/tabbrowser/crashed.svg) skin/classic/aero/browser/tabbrowser/loading.png (tabbrowser/loading.png) skin/classic/aero/browser/tabbrowser/tab-active-middle.png (tabbrowser/tab-active-middle.png) skin/classic/aero/browser/tabbrowser/tab-active-middle@2x.png (tabbrowser/tab-active-middle@2x.png) diff --git a/toolkit/themes/linux/global/in-content/common.css b/toolkit/themes/linux/global/in-content/common.css index 0daf602755e..92df1b3d4f7 100644 --- a/toolkit/themes/linux/global/in-content/common.css +++ b/toolkit/themes/linux/global/in-content/common.css @@ -13,6 +13,7 @@ xul|tab[selected] { } xul|button, +html|button, xul|colorpicker[type="button"], xul|menulist { margin: 2px 4px; diff --git a/toolkit/themes/osx/global/in-content/common.css b/toolkit/themes/osx/global/in-content/common.css index a03c0cfe5fd..705cb2278aa 100644 --- a/toolkit/themes/osx/global/in-content/common.css +++ b/toolkit/themes/osx/global/in-content/common.css @@ -15,12 +15,14 @@ xul|tab[selected] { } xul|button, +html|button, xul|colorpicker[type="button"], xul|menulist { margin-top: 3px; } -xul|button { +xul|button, +html|button { /* use the same margin of other elements for the alignment */ margin-left: 4px; margin-right: 4px; diff --git a/toolkit/themes/shared/in-content/common.inc.css b/toolkit/themes/shared/in-content/common.inc.css index 64df7b884ce..9b34c4d9e0f 100644 --- a/toolkit/themes/shared/in-content/common.inc.css +++ b/toolkit/themes/shared/in-content/common.inc.css @@ -29,6 +29,11 @@ html|h1 { margin-bottom: .5em; } +html|hr { + border-style: solid none none none; + border-color: #c1c1c1; +} + xul|caption { -moz-appearance: none; margin: 0; @@ -406,11 +411,29 @@ html|a:hover:active, /* Checkboxes and radio buttons */ +/* Hide the actual checkbox */ +html|input[type="checkbox"] { + opacity: 0; + position: absolute; +} + +/* Create a box to style as the checkbox */ +html|input[type="checkbox"] + html|label:before { + display: inline-block; + content: ""; + vertical-align: middle; +} + +html|input[type="checkbox"] + html|label { + line-height: 0px; +} + xul|checkbox { -moz-margin-start: 0; } -xul|*.checkbox-check { +xul|*.checkbox-check, +html|input[type="checkbox"] + html|label:before { -moz-appearance: none; width: 23px; height: 23px; @@ -425,7 +448,8 @@ xul|*.checkbox-check { box-shadow: 0 1px 1px 0 #fff, inset 0 2px 0 0 rgba(0,0,0,0.03); } -xul|checkbox:not([disabled="true"]):hover > xul|*.checkbox-check { +xul|checkbox:not([disabled="true"]):hover > xul|*.checkbox-check, +html|input[type="checkbox"]:not(:disabled) + html|label:hover:before { border-color: #0095dd; } @@ -433,7 +457,12 @@ xul|*.checkbox-check[checked] { list-style-image: url("chrome://global/skin/in-content/check.svg#check"); } -xul|checkbox[disabled="true"] > xul|*.checkbox-check { +html|input[type="checkbox"]:checked + html|label:before { + background-image: url("chrome://global/skin/in-content/check.svg#check"), linear-gradient(#fff, rgba(255,255,255,0.8)) !important; +} + +xul|checkbox[disabled="true"] > xul|*.checkbox-check, +html|input[type="checkbox"]:disabled + html|label { opacity: 0.5; } diff --git a/toolkit/themes/windows/global/in-content/common.css b/toolkit/themes/windows/global/in-content/common.css index 429d2e0cef3..d8dc3bc952f 100644 --- a/toolkit/themes/windows/global/in-content/common.css +++ b/toolkit/themes/windows/global/in-content/common.css @@ -9,6 +9,7 @@ xul|caption { } xul|button, +html|button, xul|colorpicker[type="button"], xul|menulist { margin: 2px 4px; From af3bd11fc6e43066a969809169872210274b6ae0 Mon Sep 17 00:00:00 2001 From: Dave Townsend Date: Tue, 13 Jan 2015 12:35:57 -0800 Subject: [PATCH 008/101] Bug 1109650: Add a button to restore all crashed tabs to about:tabcrashed. r=ttaubert --- browser/base/content/aboutTabCrashed.js | 4 + browser/base/content/aboutTabCrashed.xhtml | 2 + browser/base/content/browser.js | 21 ++++-- browser/base/content/tabbrowser.xml | 4 + .../sessionstore/test/browser_crashedTabs.js | 74 +++++++++++++++---- .../en-US/chrome/browser/aboutTabCrashed.dtd | 1 + 6 files changed, 87 insertions(+), 19 deletions(-) diff --git a/browser/base/content/aboutTabCrashed.js b/browser/base/content/aboutTabCrashed.js index 989aa8d1468..a0abedc0e58 100644 --- a/browser/base/content/aboutTabCrashed.js +++ b/browser/base/content/aboutTabCrashed.js @@ -38,6 +38,10 @@ function restoreTab() { sendEvent("restoreTab"); } +function restoreAll() { + sendEvent("restoreAll"); +} + // Error pages are loaded as LOAD_BACKGROUND, so they don't get load events. var event = new CustomEvent("AboutTabCrashedLoad", {bubbles:true}); document.dispatchEvent(event); diff --git a/browser/base/content/aboutTabCrashed.xhtml b/browser/base/content/aboutTabCrashed.xhtml index d15c662623b..ec1f37161bf 100644 --- a/browser/base/content/aboutTabCrashed.xhtml +++ b/browser/base/content/aboutTabCrashed.xhtml @@ -48,6 +48,8 @@ &tabCrashed.closeTab; + diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 113e80c834c..a5d21f6ab70 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -271,6 +271,12 @@ XPCOMUtils.defineLazyGetter(this, "PageMenuParent", function() { return new tmp.PageMenuParent(); }); +function* browserWindows() { + let windows = Services.wm.getEnumerator("navigator:browser"); + while (windows.hasMoreElements()) + yield windows.getNext(); +} + /** * We can avoid adding multiple load event listeners and save some time by adding * one listener that calls all real handlers. @@ -1143,6 +1149,13 @@ var gBrowserInit = { case "restoreTab": SessionStore.reviveCrashedTab(tab); break; + case "restoreAll": + for (let browserWin of browserWindows()) { + for (let tab of window.gBrowser.tabs) { + SessionStore.reviveCrashedTab(tab); + } + } + break; } }, false, true); @@ -6479,11 +6492,9 @@ function warnAboutClosingWindow() { return gBrowser.warnAboutClosingTabs(gBrowser.closingTabsEnum.ALL); // Figure out if there's at least one other browser window around. - let e = Services.wm.getEnumerator("navigator:browser"); let otherPBWindowExists = false; let nonPopupPresent = false; - while (e.hasMoreElements()) { - let win = e.getNext(); + for (let win of browserWindows()) { if (!win.closed && win != window) { if (isPBWindow && PrivateBrowsingUtils.isWindowPrivate(win)) otherPBWindowExists = true; @@ -7582,9 +7593,7 @@ function switchToTabHavingURI(aURI, aOpenNew, aOpenParams={}) { if (isBrowserWindow && switchIfURIInWindow(window)) return true; - let winEnum = Services.wm.getEnumerator("navigator:browser"); - while (winEnum.hasMoreElements()) { - let browserWin = winEnum.getNext(); + for (let browserWin of browserWindows()) { // Skip closed (but not yet destroyed) windows, // and the current window (which was checked earlier). if (browserWin.closed || browserWin == window) diff --git a/browser/base/content/tabbrowser.xml b/browser/base/content/tabbrowser.xml index a419c9929ad..963467ee37c 100644 --- a/browser/base/content/tabbrowser.xml +++ b/browser/base/content/tabbrowser.xml @@ -1488,6 +1488,10 @@ if (aShouldBeRemote) { tab.setAttribute("remote", "true"); + // Switching the browser to be remote will connect to a new child + // process so the browser can no longer be considered to be + // crashed. + tab.removeAttribute("crashed"); } else { tab.removeAttribute("remote"); aBrowser.messageManager.sendAsyncMessage("Browser:AppTab", { isAppTab: tab.pinned }) diff --git a/browser/components/sessionstore/test/browser_crashedTabs.js b/browser/components/sessionstore/test/browser_crashedTabs.js index 995bc14bf19..e64c304573a 100644 --- a/browser/components/sessionstore/test/browser_crashedTabs.js +++ b/browser/components/sessionstore/test/browser_crashedTabs.js @@ -12,6 +12,9 @@ registerCleanupFunction(() => { Services.prefs.clearUserPref("browser.tabs.animate"); }); +// Allow tabs to restore on demand so we can test pending states +Services.prefs.clearUserPref("browser.sessionstore.restore_on_demand"); + /** * Returns a Promise that resolves once a remote has experienced * a crash. Also does the job of cleaning up the minidump of the crash. @@ -217,8 +220,6 @@ add_task(function test_crash_page_not_in_history() { // Crash the tab yield crashBrowser(browser); - // Flush out any notifications from the crashed browser. - TabState.flush(browser); // Check the tab state and make sure the tab crashed page isn't // mentioned. @@ -248,8 +249,6 @@ add_task(function test_revived_history_from_remote() { // Crash the tab yield crashBrowser(browser); - // Flush out any notifications from the crashed browser. - TabState.flush(browser); // Browse to a new site that will cause the browser to // become remote again. @@ -289,8 +288,6 @@ add_task(function test_revived_history_from_non_remote() { // Crash the tab yield crashBrowser(browser); - // Flush out any notifications from the crashed browser. - TabState.flush(browser); // Browse to a new site that will not cause the browser to // become remote again. @@ -341,15 +338,11 @@ add_task(function test_revive_tab_from_session_store() { // Crash the tab yield crashBrowser(browser); - is(newTab2.getAttribute("crashed"), "true", "Second tab should be crashed too."); - // Flush out any notifications from the crashed browser. - TabState.flush(browser); - // Use SessionStore to revive the tab clickButton(browser, "restoreTab"); - yield promiseBrowserLoaded(browser); + yield promiseTabRestored(newTab); ok(!newTab.hasAttribute("crashed"), "Tab shouldn't be marked as crashed anymore."); is(newTab2.getAttribute("crashed"), "true", "Second tab should still be crashed though."); @@ -366,6 +359,63 @@ add_task(function test_revive_tab_from_session_store() { gBrowser.removeTab(newTab2); }); +/** + * Checks that we can revive a crashed tab back to the page that + * it was on when it crashed. + */ +add_task(function test_revive_all_tabs_from_session_store() { + let newTab = gBrowser.addTab(); + gBrowser.selectedTab = newTab; + let browser = newTab.linkedBrowser; + ok(browser.isRemoteBrowser, "Should be a remote browser"); + yield promiseBrowserLoaded(browser); + + browser.loadURI(PAGE_1); + yield promiseBrowserLoaded(browser); + + let newTab2 = gBrowser.addTab(PAGE_1); + let browser2 = newTab2.linkedBrowser; + ok(browser2.isRemoteBrowser, "Should be a remote browser"); + yield promiseBrowserLoaded(browser2); + + browser.loadURI(PAGE_1); + yield promiseBrowserLoaded(browser); + + browser.loadURI(PAGE_2); + yield promiseBrowserLoaded(browser); + + TabState.flush(browser); + TabState.flush(browser2); + + // Crash the tab + yield crashBrowser(browser); + is(newTab2.getAttribute("crashed"), "true", "Second tab should be crashed too."); + + // Use SessionStore to revive all the tabs + clickButton(browser, "restoreAll"); + yield promiseTabRestored(newTab); + ok(!newTab.hasAttribute("crashed"), "Tab shouldn't be marked as crashed anymore."); + ok(!newTab.hasAttribute("pending"), "Tab shouldn't be pending."); + ok(!newTab2.hasAttribute("crashed"), "Second tab shouldn't be marked as crashed anymore."); + ok(newTab2.hasAttribute("pending"), "Second tab should be pending."); + + gBrowser.selectedTab = newTab2; + yield promiseTabRestored(newTab2); + ok(!newTab2.hasAttribute("pending"), "Second tab shouldn't be pending."); + + // We can't just check browser.currentURI.spec, because from + // the outside, a crashed tab has the same URI as the page + // it crashed on (much like an about:neterror page). Instead, + // we have to use the documentURI on the content. + yield promiseContentDocumentURIEquals(browser, PAGE_2); + yield promiseContentDocumentURIEquals(browser2, PAGE_1); + + // We should also have two entries in the browser history. + yield promiseHistoryLength(browser, 2); + + gBrowser.removeTab(newTab); + gBrowser.removeTab(newTab2); +}); /** * Checks that about:tabcrashed can close the current tab @@ -384,8 +434,6 @@ add_task(function test_close_tab_after_crash() { // Crash the tab yield crashBrowser(browser); - // Flush out any notifications from the crashed browser. - TabState.flush(browser); let promise = promiseEvent(gBrowser.tabContainer, "TabClose"); diff --git a/browser/locales/en-US/chrome/browser/aboutTabCrashed.dtd b/browser/locales/en-US/chrome/browser/aboutTabCrashed.dtd index fa1f02c4396..609e001989e 100644 --- a/browser/locales/en-US/chrome/browser/aboutTabCrashed.dtd +++ b/browser/locales/en-US/chrome/browser/aboutTabCrashed.dtd @@ -8,3 +8,4 @@ + From b4f142aab2f64b9a32b1832b155d3e3c240a3d07 Mon Sep 17 00:00:00 2001 From: Brian Hackett Date: Fri, 30 Jan 2015 08:05:44 -0700 Subject: [PATCH 009/101] Bug 1124377 - Try to provide scratch registers for memory->memory MoveGroup moves, r=sunfish. --- js/src/jit/BacktrackingAllocator.cpp | 62 +++++++++++++++++- js/src/jit/BacktrackingAllocator.h | 1 + js/src/jit/CodeGenerator.cpp | 2 + js/src/jit/LIR-Common.h | 18 ++++++ js/src/jit/RegisterSets.h | 2 +- js/src/jit/arm/MoveEmitter-arm.h | 2 + js/src/jit/mips/MoveEmitter-mips.h | 2 + js/src/jit/none/MoveEmitter-none.h | 1 + js/src/jit/shared/MoveEmitter-x86-shared.cpp | 66 +++++++++++--------- js/src/jit/shared/MoveEmitter-x86-shared.h | 28 +++++++++ 10 files changed, 152 insertions(+), 32 deletions(-) diff --git a/js/src/jit/BacktrackingAllocator.cpp b/js/src/jit/BacktrackingAllocator.cpp index 2efb60b1961..5857ed19e83 100644 --- a/js/src/jit/BacktrackingAllocator.cpp +++ b/js/src/jit/BacktrackingAllocator.cpp @@ -128,6 +128,9 @@ BacktrackingAllocator::go() if (!populateSafepoints()) return false; + if (!annotateMoveGroups()) + return false; + return true; } @@ -1184,7 +1187,7 @@ BacktrackingAllocator::resolveControlFlow() CodePosition start = interval->start(); LNode *ins = insData[start]; - if (interval->start() > entryOf(ins->block())) { + if (start > entryOf(ins->block())) { MOZ_ASSERT(start == inputOf(ins) || start == outputOf(ins)); LiveInterval *prevInterval = reg->intervalFor(start.previous()); @@ -1454,6 +1457,63 @@ BacktrackingAllocator::populateSafepoints() return true; } +bool +BacktrackingAllocator::annotateMoveGroups() +{ + // Annotate move groups in the LIR graph with any register that is not + // allocated at that point and can be used as a scratch register. This is + // only required for x86, as other platforms always have scratch registers + // available for use. +#ifdef JS_CODEGEN_X86 + for (size_t i = 0; i < graph.numBlocks(); i++) { + if (mir->shouldCancel("Backtracking Annotate Move Groups")) + return false; + + LBlock *block = graph.getBlock(i); + LInstruction *last = nullptr; + for (LInstructionIterator iter = block->begin(); iter != block->end(); ++iter) { + if (iter->isMoveGroup()) { + CodePosition from = last ? outputOf(last) : entryOf(block); + LiveInterval::Range range(from, from.next()); + AllocatedRange search(nullptr, &range), existing; + + for (size_t i = 0; i < AnyRegister::Total; i++) { + PhysicalRegister ® = registers[i]; + if (reg.reg.isFloat() || !reg.allocatable) + continue; + + // This register is unavailable for use if (a) it is in use + // by some live interval immediately before the move group, + // or (b) it is an operand in one of the group's moves. The + // latter case handles live intervals which end immediately + // before the move group or start immediately after. + + bool found = false; + LGeneralReg alloc(reg.reg.gpr()); + for (size_t j = 0; j < iter->toMoveGroup()->numMoves(); j++) { + LMove move = iter->toMoveGroup()->getMove(j); + if (*move.from() == alloc || *move.to() == alloc) { + found = true; + break; + } + } + + if (found || reg.allocations.contains(search, &existing)) + continue; + + iter->toMoveGroup()->setScratchRegister(reg.reg.gpr()); + break; + } + } else { + last = *iter; + } + } + } +#endif + + return true; +} + void BacktrackingAllocator::dumpRegisterGroups() { diff --git a/js/src/jit/BacktrackingAllocator.h b/js/src/jit/BacktrackingAllocator.h index 82e78e59ebf..01af3192c59 100644 --- a/js/src/jit/BacktrackingAllocator.h +++ b/js/src/jit/BacktrackingAllocator.h @@ -241,6 +241,7 @@ class BacktrackingAllocator bool resolveControlFlow(); bool reifyAllocations(); bool populateSafepoints(); + bool annotateMoveGroups(); void dumpRegisterGroups(); void dumpFixedRanges(); diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index e7534e0642a..d13855dd290 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -2133,6 +2133,8 @@ CodeGenerator::visitMoveGroup(LMoveGroup *group) masm.propagateOOM(resolver.resolve()); MoveEmitter emitter(masm); + if (group->maybeScratchRegister().isGeneralReg()) + emitter.setScratchRegister(group->maybeScratchRegister().toGeneralReg()->reg()); emitter.emit(resolver); emitter.finish(); } diff --git a/js/src/jit/LIR-Common.h b/js/src/jit/LIR-Common.h index dd4f402bd25..daa258cce16 100644 --- a/js/src/jit/LIR-Common.h +++ b/js/src/jit/LIR-Common.h @@ -108,6 +108,11 @@ class LMoveGroup : public LInstructionHelper<0, 0, 0> { js::Vector moves_; +#ifdef JS_CODEGEN_X86 + // Optional general register available for use when executing moves. + LAllocation scratchRegister_; +#endif + explicit LMoveGroup(TempAllocator &alloc) : moves_(alloc) { } @@ -133,6 +138,19 @@ class LMoveGroup : public LInstructionHelper<0, 0, 0> const LMove &getMove(size_t i) const { return moves_[i]; } + +#ifdef JS_CODEGEN_X86 + void setScratchRegister(Register reg) { + scratchRegister_ = LGeneralReg(reg); + } +#endif + LAllocation maybeScratchRegister() { +#ifdef JS_CODEGEN_X86 + return scratchRegister_; +#else + return LAllocation(); +#endif + } }; diff --git a/js/src/jit/RegisterSets.h b/js/src/jit/RegisterSets.h index c2b8bd8df2f..faac3cb3025 100644 --- a/js/src/jit/RegisterSets.h +++ b/js/src/jit/RegisterSets.h @@ -405,7 +405,7 @@ class TypedRegisterSet #error "Bad architecture" #endif } - // Determemine if some register are still allocated. This function should + // Determine if some register are still allocated. This function should // be used with the set of allocatable registers used for the initialization // of the current set. bool someAllocated(const TypedRegisterSet &allocatable) const { diff --git a/js/src/jit/arm/MoveEmitter-arm.h b/js/src/jit/arm/MoveEmitter-arm.h index 11bb1064075..8f03ebdb2fd 100644 --- a/js/src/jit/arm/MoveEmitter-arm.h +++ b/js/src/jit/arm/MoveEmitter-arm.h @@ -56,6 +56,8 @@ class MoveEmitterARM ~MoveEmitterARM(); void emit(const MoveResolver &moves); void finish(); + + void setScratchRegister(Register reg) {} }; typedef MoveEmitterARM MoveEmitter; diff --git a/js/src/jit/mips/MoveEmitter-mips.h b/js/src/jit/mips/MoveEmitter-mips.h index f836e837735..ca4b1b18bd7 100644 --- a/js/src/jit/mips/MoveEmitter-mips.h +++ b/js/src/jit/mips/MoveEmitter-mips.h @@ -56,6 +56,8 @@ class MoveEmitterMIPS ~MoveEmitterMIPS(); void emit(const MoveResolver &moves); void finish(); + + void setScratchRegister(Register reg) {} }; typedef MoveEmitterMIPS MoveEmitter; diff --git a/js/src/jit/none/MoveEmitter-none.h b/js/src/jit/none/MoveEmitter-none.h index 36f3cac6f5a..f1c01e893a0 100644 --- a/js/src/jit/none/MoveEmitter-none.h +++ b/js/src/jit/none/MoveEmitter-none.h @@ -19,6 +19,7 @@ class MoveEmitterNone MoveEmitterNone(MacroAssemblerNone &) { MOZ_CRASH(); } void emit(const MoveResolver &) { MOZ_CRASH(); } void finish() { MOZ_CRASH(); } + void setScratchRegister(Register) { MOZ_CRASH(); } }; typedef MoveEmitterNone MoveEmitter; diff --git a/js/src/jit/shared/MoveEmitter-x86-shared.cpp b/js/src/jit/shared/MoveEmitter-x86-shared.cpp index 79d548ff470..8711c7c8d83 100644 --- a/js/src/jit/shared/MoveEmitter-x86-shared.cpp +++ b/js/src/jit/shared/MoveEmitter-x86-shared.cpp @@ -97,6 +97,12 @@ MoveEmitterX86::maybeEmitOptimizedCycle(const MoveResolver &moves, size_t i, void MoveEmitterX86::emit(const MoveResolver &moves) { +#if defined(JS_CODEGEN_X86) && defined(DEBUG) + // Clobber any scratch register we have, to make regalloc bugs more visible. + if (hasScratchRegister()) + masm.mov(ImmWord(0xdeadbeef), scratchRegister()); +#endif + for (size_t i = 0; i < moves.numMoves(); i++) { const MoveOp &move = moves.getMove(i); const MoveOperand &from = move.from(); @@ -365,15 +371,15 @@ MoveEmitterX86::emitInt32Move(const MoveOperand &from, const MoveOperand &to) } else { // Memory to memory gpr move. MOZ_ASSERT(from.isMemory()); -#ifdef JS_CODEGEN_X64 - // x64 has a ScratchReg. Use it. - masm.load32(toAddress(from), ScratchReg); - masm.move32(ScratchReg, toOperand(to)); -#else - // No ScratchReg; bounce it off the stack. - masm.Push(toOperand(from)); - masm.Pop(toPopOperand(to)); -#endif + if (hasScratchRegister()) { + Register reg = scratchRegister(); + masm.load32(toAddress(from), reg); + masm.move32(reg, toOperand(to)); + } else { + // No scratch register available; bounce it off the stack. + masm.Push(toOperand(from)); + masm.Pop(toPopOperand(to)); + } } } @@ -390,30 +396,30 @@ MoveEmitterX86::emitGeneralMove(const MoveOperand &from, const MoveOperand &to) masm.lea(toOperand(from), to.reg()); } else if (from.isMemory()) { // Memory to memory gpr move. -#ifdef JS_CODEGEN_X64 - // x64 has a ScratchReg. Use it. - masm.loadPtr(toAddress(from), ScratchReg); - masm.mov(ScratchReg, toOperand(to)); -#else - // No ScratchReg; bounce it off the stack. - masm.Push(toOperand(from)); - masm.Pop(toPopOperand(to)); -#endif + if (hasScratchRegister()) { + Register reg = scratchRegister(); + masm.loadPtr(toAddress(from), reg); + masm.mov(reg, toOperand(to)); + } else { + // No scratch register available; bounce it off the stack. + masm.Push(toOperand(from)); + masm.Pop(toPopOperand(to)); + } } else { // Effective address to memory move. MOZ_ASSERT(from.isEffectiveAddress()); -#ifdef JS_CODEGEN_X64 - // x64 has a ScratchReg. Use it. - masm.lea(toOperand(from), ScratchReg); - masm.mov(ScratchReg, toOperand(to)); -#else - // This is tricky without a ScratchReg. We can't do an lea. Bounce the - // base register off the stack, then add the offset in place. Note that - // this clobbers FLAGS! - masm.Push(from.base()); - masm.Pop(toPopOperand(to)); - masm.addPtr(Imm32(from.disp()), toOperand(to)); -#endif + if (hasScratchRegister()) { + Register reg = scratchRegister(); + masm.lea(toOperand(from), reg); + masm.mov(reg, toOperand(to)); + } else { + // This is tricky without a scratch reg. We can't do an lea. Bounce the + // base register off the stack, then add the offset in place. Note that + // this clobbers FLAGS! + masm.Push(from.base()); + masm.Pop(toPopOperand(to)); + masm.addPtr(Imm32(from.disp()), toOperand(to)); + } } } diff --git a/js/src/jit/shared/MoveEmitter-x86-shared.h b/js/src/jit/shared/MoveEmitter-x86-shared.h index 0f9a7e109ac..16382641db5 100644 --- a/js/src/jit/shared/MoveEmitter-x86-shared.h +++ b/js/src/jit/shared/MoveEmitter-x86-shared.h @@ -33,6 +33,11 @@ class MoveEmitterX86 // codegen->framePushed_ at the time it is allocated. -1 if not allocated. int32_t pushedAtCycle_; +#ifdef JS_CODEGEN_X86 + // Optional scratch register for performing moves. + mozilla::Maybe scratchRegister_; +#endif + void assertDone(); Address cycleSlot(); Address toAddress(const MoveOperand &operand) const; @@ -57,6 +62,29 @@ class MoveEmitterX86 ~MoveEmitterX86(); void emit(const MoveResolver &moves); void finish(); + + void setScratchRegister(Register reg) { +#ifdef JS_CODEGEN_X86 + scratchRegister_.emplace(reg); +#endif + } + + bool hasScratchRegister() { +#ifdef JS_CODEGEN_X86 + return scratchRegister_.isSome(); +#else + return true; +#endif + } + + Register scratchRegister() { + MOZ_ASSERT(hasScratchRegister()); +#ifdef JS_CODEGEN_X86 + return scratchRegister_.value(); +#else + return ScratchReg; +#endif + } }; typedef MoveEmitterX86 MoveEmitter; From 195c67fb21228bde2e04c5e7f1bdd1e11dfe4376 Mon Sep 17 00:00:00 2001 From: Martin Husemann Date: Thu, 29 Jan 2015 04:40:00 -0500 Subject: [PATCH 010/101] Bug 994133 - Make sure allocated memory has the upper 17 bits clear on NetBSD/sparc64. r=luke --- js/src/gc/Memory.cpp | 4 ++-- js/src/jsapi-tests/testGCAllocator.cpp | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/js/src/gc/Memory.cpp b/js/src/gc/Memory.cpp index 52e7e390146..3eacf07f65f 100644 --- a/js/src/gc/Memory.cpp +++ b/js/src/gc/Memory.cpp @@ -379,7 +379,7 @@ static inline void * MapMemoryAt(void *desired, size_t length, int prot = PROT_READ | PROT_WRITE, int flags = MAP_PRIVATE | MAP_ANON, int fd = -1, off_t offset = 0) { -#if defined(__ia64__) +#if defined(__ia64__) || (defined(__sparc64__) && defined(__NetBSD__)) MOZ_ASSERT(0xffff800000000000ULL & (uintptr_t(desired) + length - 1) == 0); #endif void *region = mmap(desired, length, prot, flags, fd, offset); @@ -402,7 +402,7 @@ static inline void * MapMemory(size_t length, int prot = PROT_READ | PROT_WRITE, int flags = MAP_PRIVATE | MAP_ANON, int fd = -1, off_t offset = 0) { -#if defined(__ia64__) +#if defined(__ia64__) || (defined(__sparc64__) && defined(__NetBSD__)) /* * The JS engine assumes that all allocated pointers have their high 17 bits clear, * which ia64's mmap doesn't support directly. However, we can emulate it by passing diff --git a/js/src/jsapi-tests/testGCAllocator.cpp b/js/src/jsapi-tests/testGCAllocator.cpp index 6185ef53fe9..4247853b38c 100644 --- a/js/src/jsapi-tests/testGCAllocator.cpp +++ b/js/src/jsapi-tests/testGCAllocator.cpp @@ -257,7 +257,7 @@ unmapPages(void *p, size_t size) void * mapMemoryAt(void *desired, size_t length) { -#if defined(__ia64__) +#if defined(__ia64__) || (defined(__sparc64__) && defined(__NetBSD__)) MOZ_RELEASE_ASSERT(0xffff800000000000ULL & (uintptr_t(desired) + length - 1) == 0); #endif void *region = mmap(desired, length, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); @@ -275,13 +275,13 @@ void * mapMemory(size_t length) { void *hint = nullptr; -#if defined(__ia64__) +#if defined(__ia64__) || (defined(__sparc64__) && defined(__NetBSD__)) hint = (void*)0x0000070000000000ULL; #endif void *region = mmap(hint, length, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); if (region == MAP_FAILED) return nullptr; -#if defined(__ia64__) +#if defined(__ia64__) || (defined(__sparc64__) && defined(__NetBSD__)) if ((uintptr_t(region) + (length - 1)) & 0xffff800000000000ULL) { if (munmap(region, length)) MOZ_RELEASE_ASSERT(errno == ENOMEM); From 33e9e6260b6958c85fcb89432100d0da6d2f6f33 Mon Sep 17 00:00:00 2001 From: ZongShen Shen Date: Mon, 26 Jan 2015 09:08:17 -0800 Subject: [PATCH 011/101] Bug 1120069 - Recover_TruncateToInt32: Change ToNumber() to ToInt32() for value range analysis. r=nbp --- js/src/jit/Recover.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/js/src/jit/Recover.cpp b/js/src/jit/Recover.cpp index b0bfada9ecf..ef035c05b7a 100644 --- a/js/src/jit/Recover.cpp +++ b/js/src/jit/Recover.cpp @@ -6,6 +6,7 @@ #include "jit/Recover.h" +#include "jsapi.h" #include "jscntxt.h" #include "jsmath.h" #include "jsobj.h" @@ -1142,12 +1143,11 @@ RTruncateToInt32::recover(JSContext *cx, SnapshotIterator &iter) const RootedValue value(cx, iter.read()); RootedValue result(cx); - double in; - if (!ToNumber(cx, value, &in)) + int32_t trunc; + if (!JS::ToInt32(cx, value, &trunc)) return false; - int out = ToInt32(in); - result.setInt32(out); + result.setInt32(trunc); iter.storeInstructionResult(result); return true; } From bf0df2f52b6a5acdddcc5397daf6acef5b4c28a1 Mon Sep 17 00:00:00 2001 From: Todd Whiteman Date: Thu, 22 Jan 2015 19:36:00 -0500 Subject: [PATCH 012/101] Bug 1124984 - Force the gzip call to run. r=ted --- toolkit/crashreporter/tools/symbolstore.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toolkit/crashreporter/tools/symbolstore.py b/toolkit/crashreporter/tools/symbolstore.py index 46b796213bf..f01eb047d6d 100755 --- a/toolkit/crashreporter/tools/symbolstore.py +++ b/toolkit/crashreporter/tools/symbolstore.py @@ -781,7 +781,7 @@ class Dumper_Linux(Dumper): rel_path)) shutil.move(file_dbg, full_path) # gzip the shipped debug files - os.system("gzip %s" % full_path) + os.system("gzip -f %s" % full_path) self.output(sys.stdout, rel_path + ".gz") else: if os.path.isfile(file_dbg): From 10c69c894735972000562838206b0e60e4b20bd9 Mon Sep 17 00:00:00 2001 From: JW Wang Date: Wed, 28 Jan 2015 18:57:00 -0500 Subject: [PATCH 013/101] Bug 1127171 - Put mozCaptureStream operations in the same lock. r=roc --- dom/html/HTMLMediaElement.cpp | 2 -- dom/media/MediaDecoder.cpp | 30 +++++++++----------------- dom/media/MediaDecoder.h | 9 -------- dom/media/MediaDecoderStateMachine.cpp | 22 ++++++++++++++----- dom/media/MediaDecoderStateMachine.h | 3 ++- 5 files changed, 29 insertions(+), 37 deletions(-) diff --git a/dom/html/HTMLMediaElement.cpp b/dom/html/HTMLMediaElement.cpp index 81e86992d81..e0cb3441f61 100644 --- a/dom/html/HTMLMediaElement.cpp +++ b/dom/html/HTMLMediaElement.cpp @@ -1889,7 +1889,6 @@ HTMLMediaElement::CaptureStreamInternal(bool aFinishWhenEnded) // back into the output stream. out->mStream->GetStream()->ChangeExplicitBlockerCount(1); if (mDecoder) { - mDecoder->SetAudioCaptured(true); mDecoder->AddOutputStream( out->mStream->GetStream()->AsProcessedStream(), aFinishWhenEnded); } @@ -2705,7 +2704,6 @@ nsresult HTMLMediaElement::FinishDecoderSetup(MediaDecoder* aDecoder, // available immediately. mDecoder->SetResource(aStream); mDecoder->SetAudioChannel(mAudioChannel); - mDecoder->SetAudioCaptured(mAudioCaptured); mDecoder->SetVolume(mMuted ? 0.0 : mVolume); mDecoder->SetPreservesPitch(mPreservesPitch); mDecoder->SetPlaybackRate(mPlaybackRate); diff --git a/dom/media/MediaDecoder.cpp b/dom/media/MediaDecoder.cpp index 9e6b7eea649..96597e40568 100644 --- a/dom/media/MediaDecoder.cpp +++ b/dom/media/MediaDecoder.cpp @@ -263,15 +263,6 @@ void MediaDecoder::SetVolume(double aVolume) } } -void MediaDecoder::SetAudioCaptured(bool aCaptured) -{ - MOZ_ASSERT(NS_IsMainThread()); - mInitialAudioCaptured = aCaptured; - if (mDecoderStateMachine) { - mDecoderStateMachine->SetAudioCaptured(aCaptured); - } -} - void MediaDecoder::ConnectDecodedStreamToOutputStream(OutputStreamData* aStream) { NS_ASSERTION(!aStream->mPort, "Already connected?"); @@ -360,13 +351,6 @@ MediaDecoder::DecodedStreamGraphListener::NotifyEvent(MediaStreamGraph* aGraph, } } -void MediaDecoder::RecreateDecodedStreamIfNecessary(int64_t aStartTimeUSecs) -{ - if (mInitialAudioCaptured) { - RecreateDecodedStream(aStartTimeUSecs); - } -} - void MediaDecoder::DestroyDecodedStream() { MOZ_ASSERT(NS_IsMainThread()); @@ -470,9 +454,13 @@ void MediaDecoder::AddOutputStream(ProcessedMediaStream* aStream, { ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); - if (!mDecodedStream) { - RecreateDecodedStream(mDecoderStateMachine ? - int64_t(mDecoderStateMachine->GetCurrentTime()*USECS_PER_S) : 0); + if (mDecoderStateMachine) { + mDecoderStateMachine->SetAudioCaptured(); + } + if (!GetDecodedStream()) { + int64_t t = mDecoderStateMachine ? + mDecoderStateMachine->GetCurrentTimeUs() : 0; + RecreateDecodedStream(t); } OutputStreamData* os = mOutputStreams.AppendElement(); os->Init(aStream, aFinishWhenEnded); @@ -672,7 +660,9 @@ void MediaDecoder::SetStateMachineParameters() ReentrantMonitorAutoEnter mon(GetReentrantMonitor()); mDecoderStateMachine->SetDuration(mDuration); mDecoderStateMachine->SetVolume(mInitialVolume); - mDecoderStateMachine->SetAudioCaptured(mInitialAudioCaptured); + if (GetDecodedStream()) { + mDecoderStateMachine->SetAudioCaptured(); + } SetPlaybackRate(mInitialPlaybackRate); mDecoderStateMachine->SetPreservesPitch(mInitialPreservesPitch); if (mMinimizePreroll) { diff --git a/dom/media/MediaDecoder.h b/dom/media/MediaDecoder.h index eae936b49f7..1895826ac77 100644 --- a/dom/media/MediaDecoder.h +++ b/dom/media/MediaDecoder.h @@ -374,9 +374,6 @@ public: virtual void Pause(); // Adjust the speed of the playback, optionally with pitch correction, virtual void SetVolume(double aVolume); - // Sets whether audio is being captured. If it is, we won't play any - // of our audio. - virtual void SetAudioCaptured(bool aCaptured); virtual void NotifyWaitingForResourcesStatusChanged() MOZ_OVERRIDE; @@ -857,9 +854,6 @@ public: // The decoder monitor must be held. bool IsLogicallyPlaying(); - // Re-create a decoded stream if audio being captured - void RecreateDecodedStreamIfNecessary(int64_t aStartTimeUSecs); - #ifdef MOZ_EME // This takes the decoder monitor. virtual nsresult SetCDMProxy(CDMProxy* aProxy) MOZ_OVERRIDE; @@ -1068,9 +1062,6 @@ protected: // only. int64_t mDuration; - // True when playback should start with audio captured (not playing). - bool mInitialAudioCaptured; - // True if the media is seekable (i.e. supports random access). bool mMediaSeekable; diff --git a/dom/media/MediaDecoderStateMachine.cpp b/dom/media/MediaDecoderStateMachine.cpp index db2fea73b0c..98d2a557540 100644 --- a/dom/media/MediaDecoderStateMachine.cpp +++ b/dom/media/MediaDecoderStateMachine.cpp @@ -1387,11 +1387,11 @@ void MediaDecoderStateMachine::SetVolume(double volume) } } -void MediaDecoderStateMachine::SetAudioCaptured(bool aCaptured) +void MediaDecoderStateMachine::SetAudioCaptured() { NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); - ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); - if (!mAudioCaptured && aCaptured && !mStopAudioThread) { + AssertCurrentThreadInMonitor(); + if (!mAudioCaptured && !mStopAudioThread) { // Make sure the state machine runs as soon as possible. That will // stop the audio sink. // If mStopAudioThread is true then we're already stopping the audio sink @@ -1405,7 +1405,7 @@ void MediaDecoderStateMachine::SetAudioCaptured(bool aCaptured) ResyncAudioClock(); } } - mAudioCaptured = aCaptured; + mAudioCaptured = true; } double MediaDecoderStateMachine::GetCurrentTime() const @@ -1418,6 +1418,16 @@ double MediaDecoderStateMachine::GetCurrentTime() const return static_cast(mCurrentFrameTime) / static_cast(USECS_PER_S); } +int64_t MediaDecoderStateMachine::GetCurrentTimeUs() const +{ + NS_ASSERTION(NS_IsMainThread() || + OnStateMachineThread() || + OnDecodeThread(), + "Should be on main, decode, or state machine thread."); + + return mCurrentFrameTime; +} + bool MediaDecoderStateMachine::IsRealTime() const { return mScheduler->IsRealTime(); } @@ -1782,7 +1792,9 @@ MediaDecoderStateMachine::StartSeek(const SeekTarget& aTarget) DECODER_LOG("Changed state to SEEKING (to %lld)", mSeekTarget.mTime); SetState(DECODER_STATE_SEEKING); - mDecoder->RecreateDecodedStreamIfNecessary(seekTime - mStartTime); + if (mAudioCaptured) { + mDecoder->RecreateDecodedStream(seekTime - mStartTime); + } ScheduleStateMachine(); } diff --git a/dom/media/MediaDecoderStateMachine.h b/dom/media/MediaDecoderStateMachine.h index 002cae06729..0ad8144a5de 100644 --- a/dom/media/MediaDecoderStateMachine.h +++ b/dom/media/MediaDecoderStateMachine.h @@ -155,7 +155,7 @@ public: // Set the audio volume. The decoder monitor must be obtained before // calling this. void SetVolume(double aVolume); - void SetAudioCaptured(bool aCapture); + void SetAudioCaptured(); // Check if the decoder needs to become dormant state. bool IsDormantNeeded(); @@ -241,6 +241,7 @@ public: // Called from the main thread to get the current frame time. The decoder // monitor must be obtained before calling this. double GetCurrentTime() const; + int64_t GetCurrentTimeUs() const; // Clear the flag indicating that a playback position change event // is currently queued. This is called from the main thread and must From 1a6f7d27b0428b19e360edb80797fa63d7c5dc59 Mon Sep 17 00:00:00 2001 From: John Schoenick Date: Fri, 30 Jan 2015 10:54:19 -0500 Subject: [PATCH 014/101] Bug 1061967 part 1 - Move checking for special-cased plugin types to a central spot. r=bsmedberg --- dom/base/nsObjectLoadingContent.cpp | 28 ++++++++----- dom/plugins/base/nsNPAPIPluginInstance.cpp | 10 +++-- dom/plugins/base/nsPluginHost.cpp | 42 +++++++++++++++----- dom/plugins/base/nsPluginHost.h | 17 ++++++-- dom/plugins/base/nsPluginInstanceOwner.cpp | 8 ++-- dom/plugins/base/nsPluginNativeWindowWin.cpp | 40 ++++++------------- dom/plugins/base/nsPluginTags.cpp | 14 +++++-- dom/plugins/ipc/PluginModuleChild.cpp | 24 +++++------ 8 files changed, 107 insertions(+), 76 deletions(-) diff --git a/dom/base/nsObjectLoadingContent.cpp b/dom/base/nsObjectLoadingContent.cpp index f5f50379717..2dcdfa30a13 100644 --- a/dom/base/nsObjectLoadingContent.cpp +++ b/dom/base/nsObjectLoadingContent.cpp @@ -112,6 +112,13 @@ GetObjectLog() #define LOG(args) PR_LOG(GetObjectLog(), PR_LOG_DEBUG, args) #define LOG_ENABLED() PR_LOG_TEST(GetObjectLog(), PR_LOG_DEBUG) +static bool +IsJavaMIME(const nsACString & aMIMEType) +{ + return + nsPluginHost::GetSpecialType(aMIMEType) == nsPluginHost::eSpecialType_Java; +} + static bool InActiveDocument(nsIContent *aContent) { @@ -992,7 +999,7 @@ nsObjectLoadingContent::BuildParametersArray() mCachedAttributes.AppendElement(param); } - bool isJava = nsPluginHost::IsJavaMIMEType(mContentType.get()); + bool isJava = IsJavaMIME(mContentType); nsCString codebase; if (isJava) { @@ -1584,8 +1591,8 @@ nsObjectLoadingContent::UpdateObjectParameters(bool aJavaURI) if (aJavaURI || thisContent->NodeInfo()->Equals(nsGkAtoms::applet)) { nsAdoptingCString javaMIME = Preferences::GetCString(kPrefJavaMIME); newMime = javaMIME; - NS_ASSERTION(nsPluginHost::IsJavaMIMEType(newMime.get()), - "plugin.mime.java should be recognized by IsJavaMIMEType"); + NS_ASSERTION(IsJavaMIME(newMime), + "plugin.mime.java should be recognized as java"); isJava = true; } else { nsAutoString rawTypeAttr; @@ -1593,7 +1600,7 @@ nsObjectLoadingContent::UpdateObjectParameters(bool aJavaURI) if (!rawTypeAttr.IsEmpty()) { typeAttr = rawTypeAttr; CopyUTF16toUTF8(rawTypeAttr, newMime); - isJava = nsPluginHost::IsJavaMIMEType(newMime.get()); + isJava = IsJavaMIME(newMime); } } @@ -1607,8 +1614,8 @@ nsObjectLoadingContent::UpdateObjectParameters(bool aJavaURI) if (!classIDAttr.IsEmpty()) { // Our classid support is limited to 'java:' ids nsAdoptingCString javaMIME = Preferences::GetCString(kPrefJavaMIME); - NS_ASSERTION(nsPluginHost::IsJavaMIMEType(javaMIME.get()), - "plugin.mime.java should be recognized by IsJavaMIMEType"); + NS_ASSERTION(IsJavaMIME(javaMIME), + "plugin.mime.java should be recognized as java"); if (StringBeginsWith(classIDAttr, NS_LITERAL_STRING("java:")) && PluginExistsForType(javaMIME)) { newMime = javaMIME; @@ -1720,7 +1727,7 @@ nsObjectLoadingContent::UpdateObjectParameters(bool aJavaURI) (caps & eAllowPluginSkipChannel) && IsPluginEnabledByExtension(newURI, newMime)) { LOG(("OBJLC [%p]: Using extension as type hint (%s)", this, newMime.get())); - if (!isJava && nsPluginHost::IsJavaMIMEType(newMime.get())) { + if (!isJava && IsJavaMIME(newMime)) { return UpdateObjectParameters(true); } } @@ -1836,7 +1843,7 @@ nsObjectLoadingContent::UpdateObjectParameters(bool aJavaURI) } } else { newMime = channelType; - if (nsPluginHost::IsJavaMIMEType(newMime.get())) { + if (IsJavaMIME(newMime)) { // Java does not load with a channel, and being java retroactively // changes how we may have interpreted the codebase to construct this // URI above. Because the behavior here is more or less undefined, play @@ -2107,7 +2114,7 @@ nsObjectLoadingContent::LoadObject(bool aNotify, if (mType != eType_Null) { bool allowLoad = true; - if (nsPluginHost::IsJavaMIMEType(mContentType.get())) { + if (IsJavaMIME(mContentType)) { allowLoad = CheckJavaCodebase(); } int16_t contentPolicy = nsIContentPolicy::ACCEPT; @@ -3017,7 +3024,8 @@ nsObjectLoadingContent::StopPluginInstance() if (inst) { const char* mime = nullptr; if (NS_SUCCEEDED(inst->GetMIMEType(&mime)) && mime) { - if (strcmp(mime, "audio/x-pn-realaudio-plugin") == 0) { + if (nsPluginHost::GetSpecialType(nsDependentCString(mime)) == + nsPluginHost::eSpecialType_RealPlayer) { delayedStop = true; } } diff --git a/dom/plugins/base/nsNPAPIPluginInstance.cpp b/dom/plugins/base/nsNPAPIPluginInstance.cpp index 432c5535f49..6923433b584 100644 --- a/dom/plugins/base/nsNPAPIPluginInstance.cpp +++ b/dom/plugins/base/nsNPAPIPluginInstance.cpp @@ -535,8 +535,10 @@ nsresult nsNPAPIPluginInstance::SetWindow(NPWindow* window) #if MOZ_WIDGET_GTK // bug 108347, flash plugin on linux doesn't like window->width <= // 0, but Java needs wants this call. - if (!nsPluginHost::IsJavaMIMEType(mMIMEType) && window->type == NPWindowTypeWindow && - (window->width <= 0 || window->height <= 0)) { + if (window && window->type == NPWindowTypeWindow && + (window->width <= 0 || window->height <= 0) && + (nsPluginHost::GetSpecialType(nsDependentCString(mMIMEType)) != + nsPluginHost::eSpecialType_Java)) { return NS_OK; } #endif @@ -749,8 +751,8 @@ NPError nsNPAPIPluginInstance::SetWindowless(bool aWindowless) // property. (Last tested version: sl 4.0). // Changes to this code should be matched with changes in // PluginInstanceChild::InitQuirksMode. - NS_NAMED_LITERAL_CSTRING(silverlight, "application/x-silverlight"); - if (!PL_strncasecmp(mMIMEType, silverlight.get(), silverlight.Length())) { + if (nsPluginHost::GetSpecialType(nsDependentCString(mMIMEType)) == + nsPluginHost::eSpecialType_Silverlight) { mTransparent = true; } } diff --git a/dom/plugins/base/nsPluginHost.cpp b/dom/plugins/base/nsPluginHost.cpp index f36444b6054..30838267a50 100644 --- a/dom/plugins/base/nsPluginHost.cpp +++ b/dom/plugins/base/nsPluginHost.cpp @@ -1652,19 +1652,43 @@ nsPluginHost::SiteHasData(nsIPluginTag* plugin, const nsACString& domain, return NS_OK; } -bool nsPluginHost::IsJavaMIMEType(const char* aType) +nsPluginHost::SpecialType +nsPluginHost::GetSpecialType(const nsACString & aMIMEType) { + if (aMIMEType.LowerCaseEqualsASCII("application/x-shockwave-flash") || + aMIMEType.LowerCaseEqualsASCII("application/futuresplash")) { + return eSpecialType_Flash; + } + + if (aMIMEType.LowerCaseEqualsASCII("application/x-silverlight") || + aMIMEType.LowerCaseEqualsASCII("application/x-silverlight-2")) { + return eSpecialType_Silverlight; + } + + if (aMIMEType.LowerCaseEqualsASCII("audio/x-pn-realaudio-plugin")) { + NS_WARNING("You are loading RealPlayer"); + return eSpecialType_RealPlayer; + } + + if (aMIMEType.LowerCaseEqualsASCII("application/pdf")) { + return eSpecialType_PDF; + } + + // Java registers variants of its MIME with parameters, e.g. + // application/x-java-vm;version=1.3 + const nsACString &noParam = Substring(aMIMEType, 0, aMIMEType.FindChar(';')); + // The java mime pref may well not be one of these, // e.g. application/x-java-test used in the test suite nsAdoptingCString javaMIME = Preferences::GetCString(kPrefJavaMIME); - return aType && - (javaMIME.EqualsIgnoreCase(aType) || - (0 == PL_strncasecmp(aType, "application/x-java-vm", - sizeof("application/x-java-vm") - 1)) || - (0 == PL_strncasecmp(aType, "application/x-java-applet", - sizeof("application/x-java-applet") - 1)) || - (0 == PL_strncasecmp(aType, "application/x-java-bean", - sizeof("application/x-java-bean") - 1))); + if ((!javaMIME.IsEmpty() && noParam.LowerCaseEqualsASCII(javaMIME)) || + noParam.LowerCaseEqualsASCII("application/x-java-vm") || + noParam.LowerCaseEqualsASCII("application/x-java-applet") || + noParam.LowerCaseEqualsASCII("application/x-java-bean")) { + return eSpecialType_Java; + } + + return eSpecialType_None; } // Check whether or not a tag is a live, valid tag, and that it's loaded. diff --git a/dom/plugins/base/nsPluginHost.h b/dom/plugins/base/nsPluginHost.h index 578e653284b..bddebada598 100644 --- a/dom/plugins/base/nsPluginHost.h +++ b/dom/plugins/base/nsPluginHost.h @@ -171,9 +171,20 @@ public: // Always returns true if plugin.allowed_types is not set static bool IsTypeWhitelisted(const char *aType); - // checks whether aTag is a "java" plugin tag (a tag for a plugin - // that does Java) - static bool IsJavaMIMEType(const char *aType); + // checks whether aType is a type we recognize for potential special handling + enum SpecialType { eSpecialType_None, + // Informs some decisions about OOP and quirks + eSpecialType_Flash, + // Binds to the tag, has various special + // rules around opening channels, codebase, ... + eSpecialType_Java, + // Some IPC quirks + eSpecialType_Silverlight, + // Native widget quirks + eSpecialType_PDF, + // Native widget quirks + eSpecialType_RealPlayer }; + static SpecialType GetSpecialType(const nsACString & aMIMEType); static nsresult PostPluginUnloadEvent(PRLibrary* aLibrary); diff --git a/dom/plugins/base/nsPluginInstanceOwner.cpp b/dom/plugins/base/nsPluginInstanceOwner.cpp index 4135cc8698f..13f8d8484bf 100644 --- a/dom/plugins/base/nsPluginInstanceOwner.cpp +++ b/dom/plugins/base/nsPluginInstanceOwner.cpp @@ -1096,10 +1096,10 @@ void nsPluginInstanceOwner::AddToCARefreshTimer() { // Flash invokes InvalidateRect for us. const char* mime = nullptr; - if (NS_SUCCEEDED(mInstance->GetMIMEType(&mime)) && mime) { - if (strcmp(mime, "application/x-shockwave-flash") == 0) { - return; - } + if (NS_SUCCEEDED(mInstance->GetMIMEType(&mime)) && mime && + nsPluginHost::GetSpecialType(nsDependentCString(mime)) == + nsPluginHost::eSpecialType_Flash) { + return; } if (!sCARefreshListeners) { diff --git a/dom/plugins/base/nsPluginNativeWindowWin.cpp b/dom/plugins/base/nsPluginNativeWindowWin.cpp index 78ef60093d7..0364d55d036 100644 --- a/dom/plugins/base/nsPluginNativeWindowWin.cpp +++ b/dom/plugins/base/nsPluginNativeWindowWin.cpp @@ -97,14 +97,6 @@ void PluginWindowEvent::Init(const PluginWindowWeakRef &ref, HWND aWnd, * nsPluginNativeWindow Windows specific class declaration */ -typedef enum { - nsPluginType_Unknown = 0, - nsPluginType_Flash, - nsPluginType_Real, - nsPluginType_PDF, - nsPluginType_Other -} nsPluginType; - class nsPluginNativeWindowWin : public nsPluginNativeWindow { public: nsPluginNativeWindowWin(); @@ -135,7 +127,7 @@ private: HWND mParentWnd; LONG_PTR mParentProc; public: - nsPluginType mPluginType; + nsPluginHost::SpecialType mPluginType; }; static bool sInMessageDispatch = false; @@ -213,7 +205,7 @@ static LRESULT CALLBACK PluginWndProcInternal(HWND hWnd, UINT msg, WPARAM wParam // Real may go into a state where it recursivly dispatches the same event // when subclassed. If this is Real, lets examine the event and drop it // on the floor if we get into this recursive situation. See bug 192914. - if (win->mPluginType == nsPluginType_Real) { + if (win->mPluginType == nsPluginHost::eSpecialType_RealPlayer) { if (sInMessageDispatch && msg == sLastMsg) return true; // Cache the last message sent @@ -291,7 +283,7 @@ static LRESULT CALLBACK PluginWndProcInternal(HWND hWnd, UINT msg, WPARAM wParam case WM_KILLFOCUS: { // RealPlayer can crash, don't process the message for those, // see bug 328675. - if (win->mPluginType == nsPluginType_Real && msg == sLastMsg) + if (win->mPluginType == nsPluginHost::eSpecialType_RealPlayer && msg == sLastMsg) return TRUE; // Make sure setfocus and killfocus get through to the widget procedure // even if they are eaten by the plugin. Also make sure we aren't calling @@ -309,7 +301,7 @@ static LRESULT CALLBACK PluginWndProcInternal(HWND hWnd, UINT msg, WPARAM wParam // Macromedia Flash plugin may flood the message queue with some special messages // (WM_USER+1) causing 100% CPU consumption and GUI freeze, see mozilla bug 132759; // we can prevent this from happening by delaying the processing such messages; - if (win->mPluginType == nsPluginType_Flash) { + if (win->mPluginType == nsPluginHost::eSpecialType_Flash) { if (ProcessFlashMessageDelayed(win, inst, hWnd, msg, wParam, lParam)) return TRUE; } @@ -403,7 +395,7 @@ SetWindowLongHookCheck(HWND hWnd, { nsPluginNativeWindowWin * win = (nsPluginNativeWindowWin *)GetProp(hWnd, NS_PLUGIN_WINDOW_PROPERTY_ASSOCIATION); - if (!win || (win && win->mPluginType != nsPluginType_Flash) || + if (!win || (win && win->mPluginType != nsPluginHost::eSpecialType_Flash) || (nIndex == GWLP_WNDPROC && newLong == reinterpret_cast(PluginWndProc))) return true; @@ -507,7 +499,7 @@ nsPluginNativeWindowWin::nsPluginNativeWindowWin() : nsPluginNativeWindow() mPrevWinProc = nullptr; mPluginWinProc = nullptr; - mPluginType = nsPluginType_Unknown; + mPluginType = nsPluginHost::eSpecialType_None; mParentWnd = nullptr; mParentProc = 0; @@ -615,18 +607,10 @@ nsresult nsPluginNativeWindowWin::CallSetWindow(nsRefPtr } // check plugin mime type and cache it if it will need special treatment later - if (mPluginType == nsPluginType_Unknown) { + if (mPluginType == nsPluginHost::eSpecialType_None) { const char* mimetype = nullptr; - aPluginInstance->GetMIMEType(&mimetype); - if (mimetype) { - if (!strcmp(mimetype, "application/x-shockwave-flash")) - mPluginType = nsPluginType_Flash; - else if (!strcmp(mimetype, "audio/x-pn-realaudio-plugin")) - mPluginType = nsPluginType_Real; - else if (!strcmp(mimetype, "application/pdf")) - mPluginType = nsPluginType_PDF; - else - mPluginType = nsPluginType_Other; + if (NS_SUCCEEDED(aPluginInstance->GetMIMEType(&mimetype)) && mimetype) { + mPluginType = nsPluginHost::GetSpecialType(nsDependentCString(mimetype)); } } @@ -649,7 +633,7 @@ nsresult nsPluginNativeWindowWin::CallSetWindow(nsRefPtr // PDF plugin v7.0.9, v8.1.3, and v9.0 subclass parent window, bug 531551 // V8.2.2 and V9.1 don't have such problem. - if (mPluginType == nsPluginType_PDF) { + if (mPluginType == nsPluginHost::eSpecialType_PDF) { HWND parent = ::GetParent((HWND)window); if (mParentWnd != parent) { NS_ASSERTION(!mParentWnd, "Plugin's parent window changed"); @@ -663,7 +647,7 @@ nsresult nsPluginNativeWindowWin::CallSetWindow(nsRefPtr SubclassAndAssociateWindow(); - if (window && mPluginType == nsPluginType_Flash && + if (window && mPluginType == nsPluginHost::eSpecialType_Flash && !GetPropW((HWND)window, L"PluginInstanceParentProperty")) { HookSetWindowLongPtr(); } @@ -741,7 +725,7 @@ nsresult nsPluginNativeWindowWin::UndoSubclassAndAssociateWindow() SetWindowLongPtr(hWnd, GWL_STYLE, style); } - if (mPluginType == nsPluginType_PDF && mParentWnd) { + if (mPluginType == nsPluginHost::eSpecialType_Flash && mParentWnd) { ::SetWindowLongPtr(mParentWnd, GWLP_WNDPROC, mParentProc); mParentWnd = nullptr; mParentProc = 0; diff --git a/dom/plugins/base/nsPluginTags.cpp b/dom/plugins/base/nsPluginTags.cpp index c0be7824285..a89f1e4009f 100644 --- a/dom/plugins/base/nsPluginTags.cpp +++ b/dom/plugins/base/nsPluginTags.cpp @@ -195,10 +195,16 @@ void nsPluginTag::InitMime(const char* const* aMimeTypes, } // Look for certain special plugins. - if (nsPluginHost::IsJavaMIMEType(mimeType.get())) { - mIsJavaPlugin = true; - } else if (mimeType.EqualsLiteral("application/x-shockwave-flash")) { - mIsFlashPlugin = true; + switch (nsPluginHost::GetSpecialType(mimeType)) { + case nsPluginHost::eSpecialType_Java: + mIsJavaPlugin = true; + break; + case nsPluginHost::eSpecialType_Flash: + mIsFlashPlugin = true; + break; + case nsPluginHost::eSpecialType_None: + default: + break; } // Fill in our MIME type array. diff --git a/dom/plugins/ipc/PluginModuleChild.cpp b/dom/plugins/ipc/PluginModuleChild.cpp index 531fee526de..fbf2d8efecf 100644 --- a/dom/plugins/ipc/PluginModuleChild.cpp +++ b/dom/plugins/ipc/PluginModuleChild.cpp @@ -2077,10 +2077,10 @@ PluginModuleChild::InitQuirksModes(const nsCString& aMimeType) if (mQuirks != QUIRKS_NOT_INITIALIZED) return; mQuirks = 0; - // application/x-silverlight - // application/x-silverlight-2 - NS_NAMED_LITERAL_CSTRING(silverlight, "application/x-silverlight"); - if (FindInReadable(silverlight, aMimeType)) { + + nsPluginHost::SpecialType specialType = nsPluginHost::GetSpecialType(aMimeType); + + if (specialType == nsPluginHost::eSpecialType_Silverlight) { mQuirks |= QUIRK_SILVERLIGHT_DEFAULT_TRANSPARENT; #ifdef OS_WIN mQuirks |= QUIRK_WINLESS_TRACKPOPUP_HOOK; @@ -2089,9 +2089,7 @@ PluginModuleChild::InitQuirksModes(const nsCString& aMimeType) } #ifdef OS_WIN - // application/x-shockwave-flash - NS_NAMED_LITERAL_CSTRING(flash, "application/x-shockwave-flash"); - if (FindInReadable(flash, aMimeType)) { + if (specialType == nsPluginHost::eSpecialType_Flash) { mQuirks |= QUIRK_WINLESS_TRACKPOPUP_HOOK; mQuirks |= QUIRK_FLASH_THROTTLE_WMUSER_EVENTS; mQuirks |= QUIRK_FLASH_HOOK_SETLONGPTR; @@ -2108,14 +2106,12 @@ PluginModuleChild::InitQuirksModes(const nsCString& aMimeType) #ifdef XP_MACOSX // Whitelist Flash and Quicktime to support offline renderer - NS_NAMED_LITERAL_CSTRING(flash, "application/x-shockwave-flash"); NS_NAMED_LITERAL_CSTRING(quicktime, "QuickTime Plugin.plugin"); - if (FindInReadable(flash, aMimeType)) { - mQuirks |= QUIRK_FLASH_AVOID_CGMODE_CRASHES; - mQuirks |= QUIRK_FLASH_HIDE_HIDPI_SUPPORT; - } - if (FindInReadable(flash, aMimeType) || - FindInReadable(quicktime, mPluginFilename)) { + if (specialType == nsPluginHost::eSpecialType_Flash) { + mQuirks |= QUIRK_FLASH_AVOID_CGMODE_CRASHES; + mQuirks |= QUIRK_ALLOW_OFFLINE_RENDERER; + mQuirks |= QUIRK_FLASH_HIDE_HIDPI_SUPPORT; + } else if (FindInReadable(quicktime, mPluginFilename)) { mQuirks |= QUIRK_ALLOW_OFFLINE_RENDERER; } #endif From 524d0e597d1e57972e945e3f1e25b8539803548c Mon Sep 17 00:00:00 2001 From: John Schoenick Date: Fri, 30 Jan 2015 10:54:19 -0500 Subject: [PATCH 015/101] Bug 1061967 part 2 - Bonus whitespace cleanup. r=bsmedberg --- dom/plugins/base/nsPluginNativeWindowWin.cpp | 38 ++++++++++---------- dom/plugins/ipc/PluginModuleChild.cpp | 4 +-- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/dom/plugins/base/nsPluginNativeWindowWin.cpp b/dom/plugins/base/nsPluginNativeWindowWin.cpp index 0364d55d036..500650a4a8a 100644 --- a/dom/plugins/base/nsPluginNativeWindowWin.cpp +++ b/dom/plugins/base/nsPluginNativeWindowWin.cpp @@ -98,7 +98,7 @@ void PluginWindowEvent::Init(const PluginWindowWeakRef &ref, HWND aWnd, */ class nsPluginNativeWindowWin : public nsPluginNativeWindow { -public: +public: nsPluginNativeWindowWin(); virtual ~nsPluginNativeWindowWin(); @@ -154,7 +154,7 @@ static bool ProcessFlashMessageDelayed(nsPluginNativeWindowWin * aWin, nsNPAPIPl nsCOMPtr pwe = aWin->GetPluginWindowEvent(hWnd, msg, wParam, lParam); if (pwe) { NS_DispatchToCurrentThread(pwe); - return true; + return true; } return false; } @@ -175,7 +175,7 @@ private: NS_IMETHODIMP nsDelayedPopupsEnabledEvent::Run() { mInst->PushPopupsEnabledState(false); - return NS_OK; + return NS_OK; } static LRESULT CALLBACK PluginWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); @@ -255,7 +255,7 @@ static LRESULT CALLBACK PluginWndProcInternal(HWND hWnd, UINT msg, WPARAM wParam case WM_MOUSEACTIVATE: { // If a child window of this plug-in is already focused, - // don't focus the parent to avoid focus dance. We'll + // don't focus the parent to avoid focus dance. We'll // receive a follow up WM_SETFOCUS which will notify // the appropriate window anyway. HWND focusedWnd = ::GetFocus(); @@ -417,7 +417,7 @@ SetWindowLongAHook(HWND hWnd, if (SetWindowLongHookCheck(hWnd, nIndex, newLong)) return sUser32SetWindowLongAHookStub(hWnd, nIndex, newLong); - // Set flash's new subclass to get the result. + // Set flash's new subclass to get the result. LONG_PTR proc = sUser32SetWindowLongAHookStub(hWnd, nIndex, newLong); // We already checked this in SetWindowLongHookCheck @@ -446,14 +446,14 @@ SetWindowLongWHook(HWND hWnd, if (SetWindowLongHookCheck(hWnd, nIndex, newLong)) return sUser32SetWindowLongWHookStub(hWnd, nIndex, newLong); - // Set flash's new subclass to get the result. + // Set flash's new subclass to get the result. LONG_PTR proc = sUser32SetWindowLongWHookStub(hWnd, nIndex, newLong); // We already checked this in SetWindowLongHookCheck nsPluginNativeWindowWin * win = (nsPluginNativeWindowWin *)GetProp(hWnd, NS_PLUGIN_WINDOW_PROPERTY_ASSOCIATION); - // Hook our subclass back up, just like we do on setwindow. + // Hook our subclass back up, just like we do on setwindow. win->SetPrevWindowProc( reinterpret_cast(sUser32SetWindowLongWHookStub(hWnd, nIndex, reinterpret_cast(PluginWndProc)))); @@ -491,11 +491,11 @@ HookSetWindowLongPtr() nsPluginNativeWindowWin::nsPluginNativeWindowWin() : nsPluginNativeWindow() { // initialize the struct fields - window = nullptr; - x = 0; - y = 0; - width = 0; - height = 0; + window = nullptr; + x = 0; + y = 0; + width = 0; + height = 0; mPrevWinProc = nullptr; mPluginWinProc = nullptr; @@ -547,10 +547,10 @@ NS_IMETHODIMP PluginWindowEvent::Run() else { // Currently not used, but added so that processing events here // is more generic. - ::CallWindowProc(win->GetWindowProc(), - hWnd, - GetMsg(), - GetWParam(), + ::CallWindowProc(win->GetWindowProc(), + hWnd, + GetMsg(), + GetWParam(), GetLParam()); } @@ -558,7 +558,7 @@ NS_IMETHODIMP PluginWindowEvent::Run() return NS_OK; } -PluginWindowEvent * +PluginWindowEvent * nsPluginNativeWindowWin::GetPluginWindowEvent(HWND aWnd, UINT aMsg, WPARAM aWParam, LPARAM aLParam) { if (!mWeakRef) { @@ -572,7 +572,7 @@ nsPluginNativeWindowWin::GetPluginWindowEvent(HWND aWnd, UINT aMsg, WPARAM aWPar // We have the ability to alloc if needed in case in the future some plugin // should post multiple PostMessages. However, this could lead to many // alloc's per second which could become a performance issue. See bug 169247. - if (!mCachedPluginWindowEvent) + if (!mCachedPluginWindowEvent) { event = new PluginWindowEvent(); if (!event) return nullptr; @@ -698,7 +698,7 @@ nsresult nsPluginNativeWindowWin::SubclassAndAssociateWindow() DebugOnly win = (nsPluginNativeWindowWin *)::GetProp(hWnd, NS_PLUGIN_WINDOW_PROPERTY_ASSOCIATION); NS_ASSERTION(!win || (win == this), "plugin window already has property and this is not us"); - + if (!::SetProp(hWnd, NS_PLUGIN_WINDOW_PROPERTY_ASSOCIATION, (HANDLE)this)) return NS_ERROR_FAILURE; diff --git a/dom/plugins/ipc/PluginModuleChild.cpp b/dom/plugins/ipc/PluginModuleChild.cpp index fbf2d8efecf..b5e3b63403f 100644 --- a/dom/plugins/ipc/PluginModuleChild.cpp +++ b/dom/plugins/ipc/PluginModuleChild.cpp @@ -2091,7 +2091,7 @@ PluginModuleChild::InitQuirksModes(const nsCString& aMimeType) #ifdef OS_WIN if (specialType == nsPluginHost::eSpecialType_Flash) { mQuirks |= QUIRK_WINLESS_TRACKPOPUP_HOOK; - mQuirks |= QUIRK_FLASH_THROTTLE_WMUSER_EVENTS; + mQuirks |= QUIRK_FLASH_THROTTLE_WMUSER_EVENTS; mQuirks |= QUIRK_FLASH_HOOK_SETLONGPTR; mQuirks |= QUIRK_FLASH_HOOK_GETWINDOWINFO; mQuirks |= QUIRK_FLASH_FIXUP_MOUSE_CAPTURE; @@ -2100,7 +2100,7 @@ PluginModuleChild::InitQuirksModes(const nsCString& aMimeType) // QuickTime plugin usually loaded with audio/mpeg mimetype NS_NAMED_LITERAL_CSTRING(quicktime, "npqtplugin"); if (FindInReadable(quicktime, mPluginFilename)) { - mQuirks |= QUIRK_QUICKTIME_AVOID_SETWINDOW; + mQuirks |= QUIRK_QUICKTIME_AVOID_SETWINDOW; } #endif From b5bff3fbc4725d6983bd8b099ff4295dc62f23ef Mon Sep 17 00:00:00 2001 From: Joshua Cranmer Date: Fri, 30 Jan 2015 09:54:00 -0600 Subject: [PATCH 016/101] Bug 934170: Make mach xpcshell-test work for comm-central, r=ted --- python/mozbuild/mozbuild/controller/clobber.py | 4 ++++ testing/xpcshell/mach_commands.py | 10 +++------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/python/mozbuild/mozbuild/controller/clobber.py b/python/mozbuild/mozbuild/controller/clobber.py index 34be8966962..c214762a013 100644 --- a/python/mozbuild/mozbuild/controller/clobber.py +++ b/python/mozbuild/mozbuild/controller/clobber.py @@ -52,6 +52,10 @@ class Clobberer(object): self.src_clobber = os.path.join(topsrcdir, 'CLOBBER') self.obj_clobber = os.path.join(topobjdir, 'CLOBBER') + # Try looking for mozilla/CLOBBER, for comm-central + if not os.path.isfile(self.src_clobber): + self.src_clobber = os.path.join(topsrcdir, 'mozilla', 'CLOBBER') + assert os.path.isfile(self.src_clobber) def clobber_needed(self): diff --git a/testing/xpcshell/mach_commands.py b/testing/xpcshell/mach_commands.py index 3fa9e822de9..26095b92408 100644 --- a/testing/xpcshell/mach_commands.py +++ b/testing/xpcshell/mach_commands.py @@ -79,6 +79,9 @@ class XPCShellRunner(MozbuildObject): if build_path not in sys.path: sys.path.append(build_path) + if not os.path.isfile(os.path.join(self.topsrcdir, 'build', 'automationutils.py')): + sys.path.append(os.path.join(self.topsrcdir, 'mozilla', 'build')) + if test_paths == ['all']: self.run_suite(interactive=interactive, keep_going=keep_going, shuffle=shuffle, sequential=sequential, @@ -391,12 +394,6 @@ class B2GXPCShellRunner(MozbuildObject): return runtestsb2g.run_remote_xpcshell(parser, options, args, log) -def is_platform_supported(cls): - """Must have a Firefox, Android or B2G build.""" - return conditions.is_android(cls) or \ - conditions.is_b2g(cls) or \ - conditions.is_firefox(cls) - @CommandProvider class MachCommands(MachCommandBase): def __init__(self, context): @@ -406,7 +403,6 @@ class MachCommands(MachCommandBase): setattr(self, attr, getattr(context, attr, None)) @Command('xpcshell-test', category='testing', - conditions=[is_platform_supported], description='Run XPCOM Shell tests (API direct unit testing)', parser=_parser) @CommandArgument('test_paths', default='all', nargs='*', metavar='TEST', From c4d8457b780a9ebbc70134c8844d816311efafb4 Mon Sep 17 00:00:00 2001 From: Kannan Vijayan Date: Fri, 30 Jan 2015 11:27:39 -0500 Subject: [PATCH 017/101] Bug 1124070 - Add test for regression. r=jandem --- js/src/builtin/TestingFunctions.cpp | 87 +++++++++++++++++++ .../test-baseline-eval-frame-profiling.js | 11 +++ 2 files changed, 98 insertions(+) create mode 100644 js/src/jit-test/tests/profiler/test-baseline-eval-frame-profiling.js diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index ea8757454a0..6d784ffbdd7 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -1218,6 +1218,89 @@ DisableSPSProfiling(JSContext *cx, unsigned argc, jsval *vp) return true; } +static bool +ReadSPSProfilingStack(JSContext *cx, unsigned argc, jsval *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setUndefined(); + + if (!cx->runtime()->spsProfiler.enabled()) + args.rval().setBoolean(false); + + // Array holding physical jit stack frames. + RootedObject stack(cx, NewDenseEmptyArray(cx)); + if (!stack) + return false; + + RootedObject inlineStack(cx); + RootedObject inlineFrameInfo(cx); + RootedString frameKind(cx); + RootedString frameLabel(cx); + RootedId idx(cx); + + JS::ProfilingFrameIterator::RegisterState state; + uint32_t physicalFrameNo = 0; + const unsigned propAttrs = JSPROP_ENUMERATE; + for (JS::ProfilingFrameIterator i(cx->runtime(), state); !i.done(); ++i, ++physicalFrameNo) { + MOZ_ASSERT(i.stackAddress() != nullptr); + + // Array holding all inline frames in a single physical jit stack frame. + inlineStack = NewDenseEmptyArray(cx); + if (!inlineStack) + return false; + + JS::ProfilingFrameIterator::Frame frames[16]; + uint32_t nframes = i.extractStack(frames, 0, 16); + for (uint32_t inlineFrameNo = 0; inlineFrameNo < nframes; inlineFrameNo++) { + + // Object holding frame info. + inlineFrameInfo = NewBuiltinClassInstance(cx); + if (!inlineFrameInfo) + return false; + + const char *frameKindStr = nullptr; + switch (frames[inlineFrameNo].kind) { + case JS::ProfilingFrameIterator::Frame_Baseline: + frameKindStr = "baseline"; + break; + case JS::ProfilingFrameIterator::Frame_Ion: + frameKindStr = "ion"; + break; + case JS::ProfilingFrameIterator::Frame_AsmJS: + frameKindStr = "asmjs"; + break; + default: + frameKindStr = "unknown"; + } + frameKind = NewStringCopyZ(cx, frameKindStr); + if (!frameKind) + return false; + + if (!JS_DefineProperty(cx, inlineFrameInfo, "kind", frameKind, propAttrs)) + return false; + + frameLabel = NewStringCopyZ(cx, frames[inlineFrameNo].label); + if (!frameLabel) + return false; + + if (!JS_DefineProperty(cx, inlineFrameInfo, "label", frameLabel, propAttrs)) + return false; + + idx = INT_TO_JSID(inlineFrameNo); + if (!JS_DefinePropertyById(cx, inlineStack, idx, inlineFrameInfo, 0)) + return false; + } + + // Push inline array into main array. + idx = INT_TO_JSID(physicalFrameNo); + if (!JS_DefinePropertyById(cx, stack, idx, inlineStack, 0)) + return false; + } + + args.rval().setObject(*stack); + return true; +} + static bool EnableOsiPointRegisterChecks(JSContext *, unsigned argc, jsval *vp) { @@ -2435,6 +2518,10 @@ gc::ZealModeHelpText), "disableSPSProfiling()", " Disables SPS instrumentation"), + JS_FN_HELP("readSPSProfilingStack", ReadSPSProfilingStack, 0, 0, +"readSPSProfilingStack()", +" Reads the jit stack using ProfilingFrameIterator."), + JS_FN_HELP("enableOsiPointRegisterChecks", EnableOsiPointRegisterChecks, 0, 0, "enableOsiPointRegisterChecks()", "Emit extra code to verify live regs at the start of a VM call are not\n" diff --git a/js/src/jit-test/tests/profiler/test-baseline-eval-frame-profiling.js b/js/src/jit-test/tests/profiler/test-baseline-eval-frame-profiling.js new file mode 100644 index 00000000000..6fd2a5a16ff --- /dev/null +++ b/js/src/jit-test/tests/profiler/test-baseline-eval-frame-profiling.js @@ -0,0 +1,11 @@ + +setJitCompilerOption("baseline.warmup.trigger", 10); + +function main() { + for (var i = 0; i < 50; i++) + eval("for (var j = 0; j < 50; j++) readSPSProfilingStack();"); +} + +gczeal(2, 10000); +enableSPSProfilingWithSlowAssertions(); +main(); From 93798499c73bf7e2b78e334f158335e765a9dbe7 Mon Sep 17 00:00:00 2001 From: Kannan Vijayan Date: Fri, 30 Jan 2015 11:30:29 -0500 Subject: [PATCH 018/101] Bug 1124036 - Make ARM-simulator single-stepping callback no-op when profiling is off. r=luke --- js/src/shell/js.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index a1054a06cd2..671fbb7db3d 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -4171,6 +4171,10 @@ SingleStepCallback(void *arg, jit::Simulator *sim, void *pc) { JSRuntime *rt = reinterpret_cast(arg); + // If profiling is not enabled, don't do anything. + if (!rt->spsProfiler.enabled()) + return; + JS::ProfilingFrameIterator::RegisterState state; state.pc = pc; state.sp = (void*)sim->get_register(jit::Simulator::sp); From a1ee08cca357187c0335b43819ce3dd12a54427d Mon Sep 17 00:00:00 2001 From: Jim Mathies Date: Fri, 30 Jan 2015 10:37:03 -0600 Subject: [PATCH 019/101] Bug 1127379 - Make TabParent::GetWidgetNativeData less failure prone. r=roc --- dom/ipc/TabParent.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dom/ipc/TabParent.cpp b/dom/ipc/TabParent.cpp index 7244d4afde6..eb157fcf4e1 100644 --- a/dom/ipc/TabParent.cpp +++ b/dom/ipc/TabParent.cpp @@ -2126,6 +2126,7 @@ TabParent::RecvGetDefaultScale(double* aValue) bool TabParent::RecvGetWidgetNativeData(WindowsHandle* aValue) { + *aValue = 0; nsCOMPtr content = do_QueryInterface(mFrameElement); if (content) { nsIPresShell* shell = content->OwnerDoc()->GetShell(); @@ -2136,11 +2137,10 @@ TabParent::RecvGetWidgetNativeData(WindowsHandle* aValue) if (widget) { *aValue = reinterpret_cast( widget->GetNativeData(NS_NATIVE_SHAREABLE_WINDOW)); - return true; } } } - return false; + return true; } bool From 12ee8a591d6d53833e34326b35b7dd61c0cd26fb Mon Sep 17 00:00:00 2001 From: Jim Mathies Date: Fri, 30 Jan 2015 10:37:03 -0600 Subject: [PATCH 020/101] Bug 1127374 - Make ContentParent::RecvLoadPlugin less failure prone. r=billm --- dom/ipc/ContentChild.cpp | 5 ++++- dom/ipc/ContentParent.cpp | 10 ++++++---- dom/ipc/ContentParent.h | 4 ++-- dom/ipc/PContent.ipdl | 4 ++-- dom/plugins/ipc/PluginBridge.h | 2 +- dom/plugins/ipc/PluginModuleParent.cpp | 13 ++++++++----- 6 files changed, 23 insertions(+), 15 deletions(-) diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp index 690becc1d95..539e4fa7055 100644 --- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -2484,7 +2484,10 @@ ContentChild::RecvGetProfile(nsCString* aProfile) bool ContentChild::RecvLoadPluginResult(const uint32_t& aPluginId, const bool& aResult) { - bool finalResult = aResult && SendConnectPluginBridge(aPluginId); + nsresult rv; + bool finalResult = aResult && + SendConnectPluginBridge(aPluginId, &rv) && + NS_SUCCEEDED(rv); plugins::PluginModuleContentParent::OnLoadPluginResult(aPluginId, finalResult); return true; diff --git a/dom/ipc/ContentParent.cpp b/dom/ipc/ContentParent.cpp index 77e0a0f80bc..4e1c61a076f 100755 --- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -975,15 +975,17 @@ static nsIDocShell* GetOpenerDocShellHelper(Element* aFrameElement) } bool -ContentParent::RecvLoadPlugin(const uint32_t& aPluginId) +ContentParent::RecvLoadPlugin(const uint32_t& aPluginId, nsresult* aRv) { - return mozilla::plugins::SetupBridge(aPluginId, this); + *aRv = NS_OK; + return mozilla::plugins::SetupBridge(aPluginId, this, false, aRv); } bool -ContentParent::RecvConnectPluginBridge(const uint32_t& aPluginId) +ContentParent::RecvConnectPluginBridge(const uint32_t& aPluginId, nsresult* aRv) { - return mozilla::plugins::SetupBridge(aPluginId, this, true); + *aRv = NS_OK; + return mozilla::plugins::SetupBridge(aPluginId, this, true, aRv); } bool diff --git a/dom/ipc/ContentParent.h b/dom/ipc/ContentParent.h index 7e486f7c671..2ea1d0b92f7 100644 --- a/dom/ipc/ContentParent.h +++ b/dom/ipc/ContentParent.h @@ -153,8 +153,8 @@ public: TabId* aTabId) MOZ_OVERRIDE; virtual bool RecvBridgeToChildProcess(const ContentParentId& aCpId) MOZ_OVERRIDE; - virtual bool RecvLoadPlugin(const uint32_t& aPluginId) MOZ_OVERRIDE; - virtual bool RecvConnectPluginBridge(const uint32_t& aPluginId) MOZ_OVERRIDE; + virtual bool RecvLoadPlugin(const uint32_t& aPluginId, nsresult* aRv) MOZ_OVERRIDE; + virtual bool RecvConnectPluginBridge(const uint32_t& aPluginId, nsresult* aRv) MOZ_OVERRIDE; virtual bool RecvFindPlugins(const uint32_t& aPluginEpoch, nsTArray* aPlugins, uint32_t* aNewPluginEpoch) MOZ_OVERRIDE; diff --git a/dom/ipc/PContent.ipdl b/dom/ipc/PContent.ipdl index 2e4cd26c1d3..40ed849315c 100644 --- a/dom/ipc/PContent.ipdl +++ b/dom/ipc/PContent.ipdl @@ -592,7 +592,7 @@ parent: * process. We use intr semantics here to ensure that the PluginModuleParent * allocation message is dispatched before LoadPlugin returns. */ - sync LoadPlugin(uint32_t pluginId); + sync LoadPlugin(uint32_t pluginId) returns (nsresult rv); /** * This call is used by asynchronous plugin instantiation to notify the @@ -600,7 +600,7 @@ parent: * the specified plugin id. When this call returns, the requested bridge * connection has been made. */ - sync ConnectPluginBridge(uint32_t aPluginId); + sync ConnectPluginBridge(uint32_t aPluginId) returns (nsresult rv); /** * This call returns the set of plugins loaded in the chrome diff --git a/dom/plugins/ipc/PluginBridge.h b/dom/plugins/ipc/PluginBridge.h index a8abcd43d70..b2b4bcf6a82 100644 --- a/dom/plugins/ipc/PluginBridge.h +++ b/dom/plugins/ipc/PluginBridge.h @@ -17,7 +17,7 @@ namespace plugins { bool SetupBridge(uint32_t aPluginId, dom::ContentParent* aContentParent, - bool aForceBridgeNow = false); + bool aForceBridgeNow, nsresult* rv); bool FindPluginsForContent(uint32_t aPluginEpoch, diff --git a/dom/plugins/ipc/PluginModuleParent.cpp b/dom/plugins/ipc/PluginModuleParent.cpp index a3c1832e42f..f72677c6a80 100755 --- a/dom/plugins/ipc/PluginModuleParent.cpp +++ b/dom/plugins/ipc/PluginModuleParent.cpp @@ -94,13 +94,14 @@ struct RunnableMethodTraits bool mozilla::plugins::SetupBridge(uint32_t aPluginId, dom::ContentParent* aContentParent, - bool aForceBridgeNow) + bool aForceBridgeNow, + nsresult* rv) { nsRefPtr host = nsPluginHost::GetInst(); nsRefPtr plugin; - nsresult rv = host->GetPluginForContentProcess(aPluginId, getter_AddRefs(plugin)); - if (NS_FAILED(rv)) { - return false; + *rv = host->GetPluginForContentProcess(aPluginId, getter_AddRefs(plugin)); + if (NS_FAILED(*rv)) { + return true; } PluginModuleChromeParent* chromeParent = static_cast(plugin->GetLibrary()); chromeParent->SetContentParent(aContentParent); @@ -293,7 +294,9 @@ PluginModuleContentParent::LoadModule(uint32_t aPluginId) * its module mapping. We fetch it from there after LoadPlugin finishes. */ dom::ContentChild* cp = dom::ContentChild::GetSingleton(); - if (!cp->SendLoadPlugin(aPluginId)) { + nsresult rv; + if (!cp->SendLoadPlugin(aPluginId, &rv) || + NS_FAILED(rv)) { return nullptr; } From d9a80da92f3faf6df1861fcd41f1df8e8fa48a20 Mon Sep 17 00:00:00 2001 From: Kyle Machulis Date: Fri, 30 Jan 2015 09:29:36 -0800 Subject: [PATCH 021/101] Bug 782751 - User Timing API Implementation; r=baku --- dom/base/PerformanceEntry.cpp | 8 +- dom/base/PerformanceEntry.h | 4 +- dom/base/PerformanceMark.cpp | 27 ++ dom/base/PerformanceMark.h | 36 +++ dom/base/PerformanceMeasure.cpp | 30 ++ dom/base/PerformanceMeasure.h | 44 +++ dom/base/PerformanceResourceTiming.cpp | 5 +- dom/base/PerformanceResourceTiming.h | 3 +- dom/base/moz.build | 4 + dom/base/nsDOMAttributeMap.cpp | 1 + dom/base/nsPerformance.cpp | 277 ++++++++++++++++-- dom/base/nsPerformance.h | 17 +- .../mochitest/general/test_interfaces.html | 4 + dom/webidl/Performance.webidl | 13 + dom/webidl/PerformanceMark.webidl | 12 + dom/webidl/PerformanceMeasure.webidl | 12 + dom/webidl/moz.build | 2 + modules/libpref/init/all.js | 3 + 18 files changed, 475 insertions(+), 27 deletions(-) create mode 100644 dom/base/PerformanceMark.cpp create mode 100644 dom/base/PerformanceMark.h create mode 100644 dom/base/PerformanceMeasure.cpp create mode 100644 dom/base/PerformanceMeasure.h create mode 100644 dom/webidl/PerformanceMark.webidl create mode 100644 dom/webidl/PerformanceMeasure.webidl diff --git a/dom/base/PerformanceEntry.cpp b/dom/base/PerformanceEntry.cpp index f52a0e79766..1472d59fe70 100644 --- a/dom/base/PerformanceEntry.cpp +++ b/dom/base/PerformanceEntry.cpp @@ -19,8 +19,12 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PerformanceEntry) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END -PerformanceEntry::PerformanceEntry(nsPerformance* aPerformance) -: mPerformance(aPerformance) +PerformanceEntry::PerformanceEntry(nsPerformance* aPerformance, + const nsAString& aName, + const nsAString& aEntryType) +: mPerformance(aPerformance), + mName(aName), + mEntryType(aEntryType) { MOZ_ASSERT(aPerformance, "Parent performance object should be provided"); } diff --git a/dom/base/PerformanceEntry.h b/dom/base/PerformanceEntry.h index d55530b3dc8..edffe8b30db 100644 --- a/dom/base/PerformanceEntry.h +++ b/dom/base/PerformanceEntry.h @@ -20,7 +20,9 @@ protected: virtual ~PerformanceEntry(); public: - explicit PerformanceEntry(nsPerformance* aPerformance); + PerformanceEntry(nsPerformance* aPerformance, + const nsAString& aName, + const nsAString& aEntryType); NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(PerformanceEntry) diff --git a/dom/base/PerformanceMark.cpp b/dom/base/PerformanceMark.cpp new file mode 100644 index 00000000000..67952b256bf --- /dev/null +++ b/dom/base/PerformanceMark.cpp @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PerformanceMark.h" +#include "mozilla/dom/PerformanceMarkBinding.h" + +using namespace mozilla::dom; + +PerformanceMark::PerformanceMark(nsPerformance* aPerformance, + const nsAString& aName) +: PerformanceEntry(aPerformance, aName, NS_LITERAL_STRING("mark")) +{ + MOZ_ASSERT(aPerformance, "Parent performance object should be provided"); + mStartTime = aPerformance->GetDOMTiming()->TimeStampToDOMHighRes(mozilla::TimeStamp::Now()); +} + +PerformanceMark::~PerformanceMark() +{ +} + +JSObject* +PerformanceMark::WrapObject(JSContext* aCx) +{ + return PerformanceMarkBinding::Wrap(aCx, this); +} diff --git a/dom/base/PerformanceMark.h b/dom/base/PerformanceMark.h new file mode 100644 index 00000000000..e20a0338a82 --- /dev/null +++ b/dom/base/PerformanceMark.h @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_performancemark_h___ +#define mozilla_dom_performancemark_h___ + +#include "mozilla/dom/PerformanceEntry.h" + +namespace mozilla { +namespace dom { + +// http://www.w3.org/TR/user-timing/#performancemark +class PerformanceMark MOZ_FINAL : public PerformanceEntry +{ +public: + PerformanceMark(nsPerformance* aPerformance, + const nsAString& aName); + + virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE; + + virtual DOMHighResTimeStamp StartTime() const MOZ_OVERRIDE + { + return mStartTime; + } + +protected: + virtual ~PerformanceMark(); + DOMHighResTimeStamp mStartTime; +}; + +} // namespace dom +} // namespace mozilla + +#endif /* mozilla_dom_performancemark_h___ */ diff --git a/dom/base/PerformanceMeasure.cpp b/dom/base/PerformanceMeasure.cpp new file mode 100644 index 00000000000..90d59b89849 --- /dev/null +++ b/dom/base/PerformanceMeasure.cpp @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PerformanceMeasure.h" +#include "mozilla/dom/PerformanceMeasureBinding.h" + +using namespace mozilla::dom; + +PerformanceMeasure::PerformanceMeasure(nsPerformance* aPerformance, + const nsAString& aName, + DOMHighResTimeStamp aStartTime, + DOMHighResTimeStamp aEndTime) +: PerformanceEntry(aPerformance, aName, NS_LITERAL_STRING("measure")), + mStartTime(aStartTime), + mDuration(aEndTime - aStartTime) +{ + MOZ_ASSERT(aPerformance, "Parent performance object should be provided"); +} + +PerformanceMeasure::~PerformanceMeasure() +{ +} + +JSObject* +PerformanceMeasure::WrapObject(JSContext* aCx) +{ + return PerformanceMeasureBinding::Wrap(aCx, this); +} diff --git a/dom/base/PerformanceMeasure.h b/dom/base/PerformanceMeasure.h new file mode 100644 index 00000000000..1cc44746439 --- /dev/null +++ b/dom/base/PerformanceMeasure.h @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_performancemeasure_h___ +#define mozilla_dom_performancemeasure_h___ + +#include "mozilla/dom/PerformanceEntry.h" + +namespace mozilla { +namespace dom { + +// http://www.w3.org/TR/user-timing/#performancemeasure +class PerformanceMeasure MOZ_FINAL : public PerformanceEntry +{ +public: + PerformanceMeasure(nsPerformance* aPerformance, + const nsAString& aName, + DOMHighResTimeStamp aStartTime, + DOMHighResTimeStamp aEndTime); + + virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE; + + virtual DOMHighResTimeStamp StartTime() const MOZ_OVERRIDE + { + return mStartTime; + } + + virtual DOMHighResTimeStamp Duration() const MOZ_OVERRIDE + { + return mDuration; + } + +protected: + virtual ~PerformanceMeasure(); + DOMHighResTimeStamp mStartTime; + DOMHighResTimeStamp mDuration; +}; + +} // namespace dom +} // namespace mozilla + +#endif /* mozilla_dom_performancemeasure_h___ */ diff --git a/dom/base/PerformanceResourceTiming.cpp b/dom/base/PerformanceResourceTiming.cpp index 74b836986ec..646358e491e 100644 --- a/dom/base/PerformanceResourceTiming.cpp +++ b/dom/base/PerformanceResourceTiming.cpp @@ -23,8 +23,9 @@ NS_IMPL_ADDREF_INHERITED(PerformanceResourceTiming, PerformanceEntry) NS_IMPL_RELEASE_INHERITED(PerformanceResourceTiming, PerformanceEntry) PerformanceResourceTiming::PerformanceResourceTiming(nsPerformanceTiming* aPerformanceTiming, - nsPerformance* aPerformance) -: PerformanceEntry(aPerformance), + nsPerformance* aPerformance, + const nsAString& aName) +: PerformanceEntry(aPerformance, aName, NS_LITERAL_STRING("resource")), mTiming(aPerformanceTiming) { MOZ_ASSERT(aPerformance, "Parent performance object should be provided"); diff --git a/dom/base/PerformanceResourceTiming.h b/dom/base/PerformanceResourceTiming.h index ad04e8b321b..5d682898f03 100644 --- a/dom/base/PerformanceResourceTiming.h +++ b/dom/base/PerformanceResourceTiming.h @@ -28,7 +28,8 @@ public: PerformanceEntry) PerformanceResourceTiming(nsPerformanceTiming* aPerformanceTiming, - nsPerformance* aPerformance); + nsPerformance* aPerformance, + const nsAString& aName); virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE; diff --git a/dom/base/moz.build b/dom/base/moz.build index 7c22caf0011..5fcd8d5020a 100644 --- a/dom/base/moz.build +++ b/dom/base/moz.build @@ -181,6 +181,8 @@ EXPORTS.mozilla.dom += [ 'NodeInfoInlines.h', 'NodeIterator.h', 'PerformanceEntry.h', + 'PerformanceMark.h', + 'PerformanceMeasure.h', 'PerformanceResourceTiming.h', 'ResponsiveImageSelector.h', 'ScreenOrientation.h', @@ -315,6 +317,8 @@ UNIFIED_SOURCES += [ 'nsXMLHttpRequest.cpp', 'nsXMLNameSpaceMap.cpp', 'PerformanceEntry.cpp', + 'PerformanceMark.cpp', + 'PerformanceMeasure.cpp', 'PerformanceResourceTiming.cpp', 'ResponsiveImageSelector.cpp', 'ScriptSettings.cpp', diff --git a/dom/base/nsDOMAttributeMap.cpp b/dom/base/nsDOMAttributeMap.cpp index 53ab47b46ca..54a472d2c1b 100644 --- a/dom/base/nsDOMAttributeMap.cpp +++ b/dom/base/nsDOMAttributeMap.cpp @@ -14,6 +14,7 @@ #include "mozilla/dom/Attr.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/NamedNodeMapBinding.h" +#include "mozilla/dom/NodeInfoInlines.h" #include "nsAttrName.h" #include "nsContentUtils.h" #include "nsError.h" diff --git a/dom/base/nsPerformance.cpp b/dom/base/nsPerformance.cpp index fb3612a9d8d..57f005cad5b 100644 --- a/dom/base/nsPerformance.cpp +++ b/dom/base/nsPerformance.cpp @@ -11,17 +11,21 @@ #include "nsContentUtils.h" #include "nsIScriptSecurityManager.h" #include "nsIDOMWindow.h" +#include "nsILoadInfo.h" #include "nsIURI.h" +#include "nsThreadUtils.h" #include "PerformanceEntry.h" +#include "PerformanceMark.h" +#include "PerformanceMeasure.h" #include "PerformanceResourceTiming.h" +#include "mozilla/ErrorResult.h" #include "mozilla/dom/PerformanceBinding.h" #include "mozilla/dom/PerformanceTimingBinding.h" #include "mozilla/dom/PerformanceNavigationBinding.h" #include "mozilla/TimeStamp.h" -#include "nsThreadUtils.h" -#include "nsILoadInfo.h" using namespace mozilla; +using namespace mozilla::dom; NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsPerformanceTiming, mPerformance) @@ -325,7 +329,7 @@ nsPerformanceTiming::ResponseStart() DOMHighResTimeStamp nsPerformanceTiming::ResponseEndHighRes() { - if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) { + if (!IsInitialized()) { return mZeroTime; } if (mResponseEnd.IsNull() || @@ -350,7 +354,7 @@ nsPerformanceTiming::IsInitialized() const JSObject* nsPerformanceTiming::WrapObject(JSContext *cx) { - return dom::PerformanceTimingBinding::Wrap(cx, this); + return PerformanceTimingBinding::Wrap(cx, this); } @@ -372,7 +376,7 @@ nsPerformanceNavigation::~nsPerformanceNavigation() JSObject* nsPerformanceNavigation::WrapObject(JSContext *cx) { - return dom::PerformanceNavigationBinding::Wrap(cx, this); + return PerformanceNavigationBinding::Wrap(cx, this); } @@ -449,13 +453,13 @@ nsPerformance::Navigation() DOMHighResTimeStamp nsPerformance::Now() { - return GetDOMTiming()->TimeStampToDOMHighRes(mozilla::TimeStamp::Now()); + return GetDOMTiming()->TimeStampToDOMHighRes(TimeStamp::Now()); } JSObject* nsPerformance::WrapObject(JSContext *cx) { - return dom::PerformanceBinding::Wrap(cx, this); + return PerformanceBinding::Wrap(cx, this); } void @@ -483,7 +487,7 @@ nsPerformance::GetEntriesByType(const nsAString& entryType, void nsPerformance::GetEntriesByName(const nsAString& name, - const mozilla::dom::Optional& entryType, + const Optional& entryType, nsTArray >& retval) { MOZ_ASSERT(NS_IsMainThread()); @@ -499,11 +503,28 @@ nsPerformance::GetEntriesByName(const nsAString& name, } } +void +nsPerformance::ClearEntries(const Optional& aEntryName, + const nsAString& aEntryType) +{ + for (uint32_t i = 0; i < mEntries.Length();) { + if ((!aEntryName.WasPassed() || + mEntries[i]->GetName().Equals(aEntryName.Value())) && + (aEntryType.IsEmpty() || + mEntries[i]->GetEntryType().Equals(aEntryType))) { + mEntries.RemoveElementAt(i); + } else { + ++i; + } + } +} + void nsPerformance::ClearResourceTimings() { MOZ_ASSERT(NS_IsMainThread()); - mEntries.Clear(); + ClearEntries(Optional(), + NS_LITERAL_STRING("resource")); } void @@ -529,6 +550,7 @@ nsPerformance::AddEntry(nsIHttpChannel* channel, // Don't add the entry if the buffer is full if (mEntries.Length() >= mPrimaryBufferSize) { + NS_WARNING("Performance Entry buffer size maximum reached!"); return; } @@ -558,24 +580,16 @@ nsPerformance::AddEntry(nsIHttpChannel* channel, // The PerformanceResourceTiming object will use the nsPerformanceTiming // object to get all the required timings. - nsRefPtr performanceEntry = - new dom::PerformanceResourceTiming(performanceTiming, this); + nsRefPtr performanceEntry = + new PerformanceResourceTiming(performanceTiming, this, entryName); - performanceEntry->SetName(entryName); - performanceEntry->SetEntryType(NS_LITERAL_STRING("resource")); // If the initiator type had no valid value, then set it to the default // ("other") value. if (initiatorType.IsEmpty()) { initiatorType = NS_LITERAL_STRING("other"); } performanceEntry->SetInitiatorType(initiatorType); - - mEntries.InsertElementSorted(performanceEntry, - PerformanceEntryComparator()); - if (mEntries.Length() >= mPrimaryBufferSize) { - // call onresourcetimingbufferfull - DispatchBufferFullEvent(); - } + InsertPerformanceEntry(performanceEntry); } } @@ -598,3 +612,226 @@ nsPerformance::PerformanceEntryComparator::LessThan( "Trying to compare null performance entries"); return aElem1->StartTime() < aElem2->StartTime(); } + +void +nsPerformance::InsertPerformanceEntry(PerformanceEntry* aEntry) +{ + MOZ_ASSERT(aEntry); + MOZ_ASSERT(mEntries.Length() < mPrimaryBufferSize); + if (mEntries.Length() == mPrimaryBufferSize) { + NS_WARNING("Performance Entry buffer size maximum reached!"); + return; + } + mEntries.InsertElementSorted(aEntry, + PerformanceEntryComparator()); + if (mEntries.Length() == mPrimaryBufferSize) { + // call onresourcetimingbufferfull + DispatchBufferFullEvent(); + } +} + +void +nsPerformance::Mark(const nsAString& aName, ErrorResult& aRv) +{ + MOZ_ASSERT(NS_IsMainThread()); + // Don't add the entry if the buffer is full + if (mEntries.Length() >= mPrimaryBufferSize) { + NS_WARNING("Performance Entry buffer size maximum reached!"); + return; + } + if (IsPerformanceTimingAttribute(aName)) { + aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); + return; + } + nsRefPtr performanceMark = + new PerformanceMark(this, aName); + InsertPerformanceEntry(performanceMark); +} + +void +nsPerformance::ClearMarks(const Optional& aName) +{ + MOZ_ASSERT(NS_IsMainThread()); + ClearEntries(aName, NS_LITERAL_STRING("mark")); +} + +DOMHighResTimeStamp +nsPerformance::ResolveTimestampFromName(const nsAString& aName, + ErrorResult& aRv) +{ + nsAutoTArray, 1> arr; + DOMHighResTimeStamp ts; + Optional typeParam; + nsAutoString str; + str.AssignLiteral("mark"); + typeParam = &str; + GetEntriesByName(aName, typeParam, arr); + if (!arr.IsEmpty()) { + return arr.LastElement()->StartTime(); + } + if (!IsPerformanceTimingAttribute(aName)) { + aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); + return 0; + } + ts = GetPerformanceTimingFromString(aName); + if (!ts) { + aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); + return 0; + } + return ConvertDOMMilliSecToHighRes(ts); +} + +void +nsPerformance::Measure(const nsAString& aName, + const Optional& aStartMark, + const Optional& aEndMark, + ErrorResult& aRv) +{ + MOZ_ASSERT(NS_IsMainThread()); + // Don't add the entry if the buffer is full + if (mEntries.Length() >= mPrimaryBufferSize) { + NS_WARNING("Performance Entry buffer size maximum reached!"); + return; + } + DOMHighResTimeStamp startTime; + DOMHighResTimeStamp endTime; + + if (IsPerformanceTimingAttribute(aName)) { + aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); + return; + } + + if (aStartMark.WasPassed()) { + startTime = ResolveTimestampFromName(aStartMark.Value(), aRv); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + } else { + // Navigation start is used in this case, but since DOMHighResTimeStamp is + // in relation to navigation start, this will be zero if a name is not + // passed. + startTime = 0; + } + if (aEndMark.WasPassed()) { + endTime = ResolveTimestampFromName(aEndMark.Value(), aRv); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + } else { + endTime = Now(); + } + nsRefPtr performanceMeasure = + new PerformanceMeasure(this, aName, startTime, endTime); + InsertPerformanceEntry(performanceMeasure); +} + +void +nsPerformance::ClearMeasures(const Optional& aName) +{ + MOZ_ASSERT(NS_IsMainThread()); + ClearEntries(aName, NS_LITERAL_STRING("measure")); +} + +DOMHighResTimeStamp +nsPerformance::ConvertDOMMilliSecToHighRes(DOMTimeMilliSec aTime) { + // If the time we're trying to convert is equal to zero, it hasn't been set + // yet so just return 0. + if (aTime == 0) { + return 0; + } + return aTime - GetDOMTiming()->GetNavigationStart(); +} + +// To be removed once bug 1124165 lands +bool +nsPerformance::IsPerformanceTimingAttribute(const nsAString& aName) +{ + // Note that toJSON is added to this list due to bug 1047848 + static const char* attributes[] = + {"navigationStart", "unloadEventStart", "unloadEventEnd", "redirectStart", + "redirectEnd", "fetchStart", "domainLookupStart", "domainLookupEnd", + "connectStart", "connectEnd", "requestStart", "responseStart", + "responseEnd", "domLoading", "domInteractive", "domContentLoadedEventStart", + "domContentLoadedEventEnd", "domComplete", "loadEventStart", + "loadEventEnd", nullptr}; + + for (uint32_t i = 0; attributes[i]; ++i) { + if (aName.EqualsASCII(attributes[i])) { + return true; + } + } + return false; +} + +DOMTimeMilliSec +nsPerformance::GetPerformanceTimingFromString(const nsAString& aProperty) +{ + if (!IsPerformanceTimingAttribute(aProperty)) { + return 0; + } + if (aProperty.EqualsLiteral("navigationStart")) { + // DOMHighResTimeStamp is in relation to navigationStart, so this will be + // zero. + return GetDOMTiming()->GetNavigationStart(); + } + if (aProperty.EqualsLiteral("unloadEventStart")) { + return GetDOMTiming()->GetUnloadEventStart(); + } + if (aProperty.EqualsLiteral("unloadEventEnd")) { + return GetDOMTiming()->GetUnloadEventEnd(); + } + if (aProperty.EqualsLiteral("redirectStart")) { + return Timing()->RedirectStart(); + } + if (aProperty.EqualsLiteral("redirectEnd")) { + return Timing()->RedirectEnd(); + } + if (aProperty.EqualsLiteral("fetchStart")) { + return Timing()->FetchStart(); + } + if (aProperty.EqualsLiteral("domainLookupStart")) { + return Timing()->DomainLookupStart(); + } + if (aProperty.EqualsLiteral("domainLookupEnd")) { + return Timing()->DomainLookupEnd(); + } + if (aProperty.EqualsLiteral("connectStart")) { + return Timing()->ConnectStart(); + } + if (aProperty.EqualsLiteral("connectEnd")) { + return Timing()->ConnectEnd(); + } + if (aProperty.EqualsLiteral("requestStart")) { + return Timing()->RequestStart(); + } + if (aProperty.EqualsLiteral("responseStart")) { + return Timing()->ResponseStart(); + } + if (aProperty.EqualsLiteral("responseEnd")) { + return Timing()->ResponseEnd(); + } + if (aProperty.EqualsLiteral("domLoading")) { + return GetDOMTiming()->GetDomLoading(); + } + if (aProperty.EqualsLiteral("domInteractive")) { + return GetDOMTiming()->GetDomInteractive(); + } + if (aProperty.EqualsLiteral("domContentLoadedEventStart")) { + return GetDOMTiming()->GetDomContentLoadedEventStart(); + } + if (aProperty.EqualsLiteral("domContentLoadedEventEnd")) { + return GetDOMTiming()->GetDomContentLoadedEventEnd(); + } + if (aProperty.EqualsLiteral("domComplete")) { + return GetDOMTiming()->GetDomComplete(); + } + if (aProperty.EqualsLiteral("loadEventStart")) { + return GetDOMTiming()->GetLoadEventStart(); + } + if (aProperty.EqualsLiteral("loadEventEnd")) { + return GetDOMTiming()->GetLoadEventEnd(); + } + MOZ_CRASH("IsPerformanceTimingAttribute and GetPerformanceTimingFromString are out of sync"); + return 0; +} + diff --git a/dom/base/nsPerformance.h b/dom/base/nsPerformance.h index 57fbbf11c58..22b01538e35 100644 --- a/dom/base/nsPerformance.h +++ b/dom/base/nsPerformance.h @@ -21,6 +21,7 @@ class nsPerformance; class nsIHttpChannel; namespace mozilla { +class ErrorResult; namespace dom { class PerformanceEntry; } @@ -335,12 +336,26 @@ public: nsITimedChannel* timedChannel); void ClearResourceTimings(); void SetResourceTimingBufferSize(uint64_t maxSize); + void Mark(const nsAString& aName, mozilla::ErrorResult& aRv); + void ClearMarks(const mozilla::dom::Optional& aName); + void Measure(const nsAString& aName, + const mozilla::dom::Optional& aStartMark, + const mozilla::dom::Optional& aEndMark, + mozilla::ErrorResult& aRv); + void ClearMeasures(const mozilla::dom::Optional& aName); + IMPL_EVENT_HANDLER(resourcetimingbufferfull) private: ~nsPerformance(); + bool IsPerformanceTimingAttribute(const nsAString& aName); + DOMHighResTimeStamp ResolveTimestampFromName(const nsAString& aName, mozilla::ErrorResult& aRv); + DOMTimeMilliSec GetPerformanceTimingFromString(const nsAString& aTimingName); + DOMHighResTimeStamp ConvertDOMMilliSecToHighRes(const DOMTimeMilliSec aTime); void DispatchBufferFullEvent(); - + void InsertPerformanceEntry(PerformanceEntry* aEntry); + void ClearEntries(const mozilla::dom::Optional& aEntryName, + const nsAString& aEntryType); nsCOMPtr mWindow; nsRefPtr mDOMTiming; nsCOMPtr mChannel; diff --git a/dom/tests/mochitest/general/test_interfaces.html b/dom/tests/mochitest/general/test_interfaces.html index 8c0ffb7fd48..fac8b0093b7 100644 --- a/dom/tests/mochitest/general/test_interfaces.html +++ b/dom/tests/mochitest/general/test_interfaces.html @@ -859,6 +859,10 @@ var interfaceNamesInGlobalScope = "Performance", // IMPORTANT: Do not change this list without review from a DOM peer! "PerformanceEntry", +// IMPORTANT: Do not change this list without review from a DOM peer! + "PerformanceMark", +// IMPORTANT: Do not change this list without review from a DOM peer! + "PerformanceMeasure", // IMPORTANT: Do not change this list without review from a DOM peer! "PerformanceNavigation", // IMPORTANT: Do not change this list without review from a DOM peer! diff --git a/dom/webidl/Performance.webidl b/dom/webidl/Performance.webidl index c17e1840d38..e6274f8c732 100644 --- a/dom/webidl/Performance.webidl +++ b/dom/webidl/Performance.webidl @@ -51,3 +51,16 @@ partial interface Performance { [Pref="dom.enable_resource_timing"] attribute EventHandler onresourcetimingbufferfull; }; + +// http://www.w3.org/TR/user-timing/ +[Exposed=Window] +partial interface Performance { + [Pref="dom.enable_user_timing", Throws] + void mark(DOMString markName); + [Pref="dom.enable_user_timing"] + void clearMarks(optional DOMString markName); + [Pref="dom.enable_user_timing", Throws] + void measure(DOMString measureName, optional DOMString startMark, optional DOMString endMark); + [Pref="dom.enable_user_timing"] + void clearMeasures(optional DOMString measureName); +}; diff --git a/dom/webidl/PerformanceMark.webidl b/dom/webidl/PerformanceMark.webidl new file mode 100644 index 00000000000..fb20c334a0b --- /dev/null +++ b/dom/webidl/PerformanceMark.webidl @@ -0,0 +1,12 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * The origin of this IDL file is + * http://www.w3.org/TR/user-timing/#performancemark + */ + +interface PerformanceMark : PerformanceEntry +{ +}; diff --git a/dom/webidl/PerformanceMeasure.webidl b/dom/webidl/PerformanceMeasure.webidl new file mode 100644 index 00000000000..e203c6d066d --- /dev/null +++ b/dom/webidl/PerformanceMeasure.webidl @@ -0,0 +1,12 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * The origin of this IDL file is + * http://www.w3.org/TR/user-timing/#performancemeasure + */ + +interface PerformanceMeasure : PerformanceEntry +{ +}; diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build index ff8758687fe..de17f8544e8 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -320,6 +320,8 @@ WEBIDL_FILES = [ 'ParentNode.webidl', 'Performance.webidl', 'PerformanceEntry.webidl', + 'PerformanceMark.webidl', + 'PerformanceMeasure.webidl', 'PerformanceNavigation.webidl', 'PerformanceResourceTiming.webidl', 'PerformanceTiming.webidl', diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 3c75ea9407c..7b54ed8295d 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -144,6 +144,9 @@ pref("dom.enable_performance", true); // Whether resource timing will be gathered and returned by performance.GetEntries* pref("dom.enable_resource_timing", true); +// Enable high-resolution timing markers for users +pref("dom.enable_user_timing", true); + // Whether the Gamepad API is enabled pref("dom.gamepad.enabled", true); #ifdef RELEASE_BUILD From e7a46312fedeb80fbcbe58ef769a795ca74635fc Mon Sep 17 00:00:00 2001 From: Kyle Machulis Date: Fri, 30 Jan 2015 09:29:36 -0800 Subject: [PATCH 022/101] Bug 782751 - User Timing API Mochitests; r=baku --- dom/base/test/mochitest.ini | 2 + .../test/test_performance_user_timing.html | 306 ++++++++++++++++++ 2 files changed, 308 insertions(+) create mode 100644 dom/base/test/test_performance_user_timing.html diff --git a/dom/base/test/mochitest.ini b/dom/base/test/mochitest.ini index 8f21b6383fe..5f23800a45c 100644 --- a/dom/base/test/mochitest.ini +++ b/dom/base/test/mochitest.ini @@ -770,3 +770,5 @@ skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s [test_window_define_nonconfigurable.html] skip-if = true # bug 1107443 - code for newly-added test was disabled +[test_performance_user_timing.html] + diff --git a/dom/base/test/test_performance_user_timing.html b/dom/base/test/test_performance_user_timing.html new file mode 100644 index 00000000000..c2fa73cc3c3 --- /dev/null +++ b/dom/base/test/test_performance_user_timing.html @@ -0,0 +1,306 @@ + + + + + Test for Bug 782751 + + + + + Mozilla Bug 782751 - User Timing API +
+
+
+            
+        
+ + From ddc9fdc022e6dfa7d6e7680d9261c7c0a5614c66 Mon Sep 17 00:00:00 2001 From: Kyle Machulis Date: Fri, 30 Jan 2015 09:29:36 -0800 Subject: [PATCH 023/101] Bug 782751 - User Timing API Web Platform Test Metadata update; a=TEST-ONLY --- .../meta/user-timing/idlharness.html.ini | 54 ------------------- .../test_user_timing_clear_marks.html.ini | 5 -- .../test_user_timing_clear_measures.html.ini | 5 -- .../test_user_timing_entry_type.html.ini | 3 -- .../test_user_timing_exists.html.ini | 14 ----- .../test_user_timing_mark.html.ini | 5 +- ...ion_when_invoke_without_parameter.html.ini | 8 --- .../test_user_timing_mark_exceptions.html.ini | 5 -- .../test_user_timing_measure.html.ini | 5 -- ...st_user_timing_measure_exceptions.html.ini | 5 -- ..._timing_measure_navigation_timing.html.ini | 5 -- 11 files changed, 2 insertions(+), 112 deletions(-) delete mode 100644 testing/web-platform/meta/user-timing/test_user_timing_clear_marks.html.ini delete mode 100644 testing/web-platform/meta/user-timing/test_user_timing_clear_measures.html.ini delete mode 100644 testing/web-platform/meta/user-timing/test_user_timing_entry_type.html.ini delete mode 100644 testing/web-platform/meta/user-timing/test_user_timing_exists.html.ini delete mode 100644 testing/web-platform/meta/user-timing/test_user_timing_mark_and_measure_exception_when_invoke_without_parameter.html.ini delete mode 100644 testing/web-platform/meta/user-timing/test_user_timing_mark_exceptions.html.ini delete mode 100644 testing/web-platform/meta/user-timing/test_user_timing_measure.html.ini delete mode 100644 testing/web-platform/meta/user-timing/test_user_timing_measure_exceptions.html.ini delete mode 100644 testing/web-platform/meta/user-timing/test_user_timing_measure_navigation_timing.html.ini diff --git a/testing/web-platform/meta/user-timing/idlharness.html.ini b/testing/web-platform/meta/user-timing/idlharness.html.ini index 202a51440ac..a10b1d0a558 100644 --- a/testing/web-platform/meta/user-timing/idlharness.html.ini +++ b/testing/web-platform/meta/user-timing/idlharness.html.ini @@ -1,62 +1,8 @@ [idlharness.html] type: testharness - [Performance interface: operation mark(DOMString)] - expected: FAIL - - [Performance interface: operation clearMarks(DOMString)] - expected: FAIL - - [Performance interface: operation measure(DOMString,DOMString,DOMString)] - expected: FAIL - - [Performance interface: operation clearMeasures(DOMString)] - expected: FAIL - - [Performance interface: window.performance must inherit property "mark" with the proper type (0)] - expected: FAIL - - [Performance interface: calling mark(DOMString) on window.performance with too few arguments must throw TypeError] - expected: FAIL - - [Performance interface: window.performance must inherit property "clearMarks" with the proper type (1)] - expected: FAIL - - [Performance interface: calling clearMarks(DOMString) on window.performance with too few arguments must throw TypeError] - expected: FAIL - - [Performance interface: window.performance must inherit property "measure" with the proper type (2)] - expected: FAIL - - [Performance interface: calling measure(DOMString,DOMString,DOMString) on window.performance with too few arguments must throw TypeError] - expected: FAIL - - [Performance interface: window.performance must inherit property "clearMeasures" with the proper type (3)] - expected: FAIL - - [Performance interface: calling clearMeasures(DOMString) on window.performance with too few arguments must throw TypeError] - expected: FAIL - [PerformanceMark interface: existence and properties of interface object] expected: FAIL - [PerformanceMark interface object length] - expected: FAIL - - [PerformanceMark interface: existence and properties of interface prototype object] - expected: FAIL - - [PerformanceMark interface: existence and properties of interface prototype object\'s "constructor" property] - expected: FAIL - [PerformanceMeasure interface: existence and properties of interface object] expected: FAIL - [PerformanceMeasure interface object length] - expected: FAIL - - [PerformanceMeasure interface: existence and properties of interface prototype object] - expected: FAIL - - [PerformanceMeasure interface: existence and properties of interface prototype object\'s "constructor" property] - expected: FAIL - diff --git a/testing/web-platform/meta/user-timing/test_user_timing_clear_marks.html.ini b/testing/web-platform/meta/user-timing/test_user_timing_clear_marks.html.ini deleted file mode 100644 index 8c63f905e65..00000000000 --- a/testing/web-platform/meta/user-timing/test_user_timing_clear_marks.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[test_user_timing_clear_marks.html] - type: testharness - [The User Timing and Performance Timeline interfaces, which are required for this test, are defined.] - expected: FAIL - diff --git a/testing/web-platform/meta/user-timing/test_user_timing_clear_measures.html.ini b/testing/web-platform/meta/user-timing/test_user_timing_clear_measures.html.ini deleted file mode 100644 index ab517286b42..00000000000 --- a/testing/web-platform/meta/user-timing/test_user_timing_clear_measures.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[test_user_timing_clear_measures.html] - type: testharness - [The User Timing and Performance Timeline interfaces, which are required for this test, are defined.] - expected: FAIL - diff --git a/testing/web-platform/meta/user-timing/test_user_timing_entry_type.html.ini b/testing/web-platform/meta/user-timing/test_user_timing_entry_type.html.ini deleted file mode 100644 index 6ae5df1ab23..00000000000 --- a/testing/web-platform/meta/user-timing/test_user_timing_entry_type.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[test_user_timing_entry_type.html] - type: testharness - expected: ERROR diff --git a/testing/web-platform/meta/user-timing/test_user_timing_exists.html.ini b/testing/web-platform/meta/user-timing/test_user_timing_exists.html.ini deleted file mode 100644 index f9e7b3cafd0..00000000000 --- a/testing/web-platform/meta/user-timing/test_user_timing_exists.html.ini +++ /dev/null @@ -1,14 +0,0 @@ -[test_user_timing_exists.html] - type: testharness - [window.performance.mark is defined.] - expected: FAIL - - [window.performance.clearMarks is defined.] - expected: FAIL - - [window.performance.measure is defined.] - expected: FAIL - - [window.performance.clearMeasures is defined.] - expected: FAIL - diff --git a/testing/web-platform/meta/user-timing/test_user_timing_mark.html.ini b/testing/web-platform/meta/user-timing/test_user_timing_mark.html.ini index b7ff61f6a8f..1e2d28d7d48 100644 --- a/testing/web-platform/meta/user-timing/test_user_timing_mark.html.ini +++ b/testing/web-platform/meta/user-timing/test_user_timing_mark.html.ini @@ -1,5 +1,4 @@ [test_user_timing_mark.html] type: testharness - [The User Timing and Performance Timeline interfaces, which are required for this test, are defined.] - expected: FAIL - + disabled: + if (os == "win"): https://bugzilla.mozilla.org/show_bug.cgi?id=1127392 diff --git a/testing/web-platform/meta/user-timing/test_user_timing_mark_and_measure_exception_when_invoke_without_parameter.html.ini b/testing/web-platform/meta/user-timing/test_user_timing_mark_and_measure_exception_when_invoke_without_parameter.html.ini deleted file mode 100644 index 3195c9cf007..00000000000 --- a/testing/web-platform/meta/user-timing/test_user_timing_mark_and_measure_exception_when_invoke_without_parameter.html.ini +++ /dev/null @@ -1,8 +0,0 @@ -[test_user_timing_mark_and_measure_exception_when_invoke_without_parameter.html] - type: testharness - [window.performance.mark() interface is not supported!] - expected: FAIL - - [window.performance.measure() interface is not supported!] - expected: FAIL - diff --git a/testing/web-platform/meta/user-timing/test_user_timing_mark_exceptions.html.ini b/testing/web-platform/meta/user-timing/test_user_timing_mark_exceptions.html.ini deleted file mode 100644 index f77e05173d5..00000000000 --- a/testing/web-platform/meta/user-timing/test_user_timing_mark_exceptions.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[test_user_timing_mark_exceptions.html] - type: testharness - [The User Timing and Performance Timeline interfaces, which are required for this test, are defined.] - expected: FAIL - diff --git a/testing/web-platform/meta/user-timing/test_user_timing_measure.html.ini b/testing/web-platform/meta/user-timing/test_user_timing_measure.html.ini deleted file mode 100644 index 23eecd6e18c..00000000000 --- a/testing/web-platform/meta/user-timing/test_user_timing_measure.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[test_user_timing_measure.html] - type: testharness - [The User Timing and Performance Timeline interfaces, which are required for this test, are defined.] - expected: FAIL - diff --git a/testing/web-platform/meta/user-timing/test_user_timing_measure_exceptions.html.ini b/testing/web-platform/meta/user-timing/test_user_timing_measure_exceptions.html.ini deleted file mode 100644 index fbbc11078b8..00000000000 --- a/testing/web-platform/meta/user-timing/test_user_timing_measure_exceptions.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[test_user_timing_measure_exceptions.html] - type: testharness - [The User Timing and Performance Timeline interfaces, which are required for this test, are defined.] - expected: FAIL - diff --git a/testing/web-platform/meta/user-timing/test_user_timing_measure_navigation_timing.html.ini b/testing/web-platform/meta/user-timing/test_user_timing_measure_navigation_timing.html.ini deleted file mode 100644 index f3f4d8b809c..00000000000 --- a/testing/web-platform/meta/user-timing/test_user_timing_measure_navigation_timing.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[test_user_timing_measure_navigation_timing.html] - type: testharness - [The User Timing and Performance Timeline interfaces, which are required for this test, are defined.] - expected: FAIL - From 7d33df44326e953e6e6ea897843c924deed6aeba Mon Sep 17 00:00:00 2001 From: Ryan VanderMeulen Date: Fri, 30 Jan 2015 12:51:11 -0500 Subject: [PATCH 024/101] Backed out changeset 2c890016184e (bug 1124036) for SM(arm) failures. CLOSED TREE --- js/src/shell/js.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 671fbb7db3d..a1054a06cd2 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -4171,10 +4171,6 @@ SingleStepCallback(void *arg, jit::Simulator *sim, void *pc) { JSRuntime *rt = reinterpret_cast(arg); - // If profiling is not enabled, don't do anything. - if (!rt->spsProfiler.enabled()) - return; - JS::ProfilingFrameIterator::RegisterState state; state.pc = pc; state.sp = (void*)sim->get_register(jit::Simulator::sp); From 5108f641ee00587b15238305a3d9a3270f543f4a Mon Sep 17 00:00:00 2001 From: David Keeler Date: Thu, 15 Jan 2015 11:01:10 -0800 Subject: [PATCH 025/101] bug 832837 - move insecure form submission warning from nsSecureBrowserUIImpl to the HTML form implementation r=mrbkap r=phlsa As a result, we can remove nsSecurityWarningDialogs completely, which this patch also does. --- CLOBBER | 2 +- dom/html/HTMLFormElement.cpp | 102 +++++-- security/manager/boot/public/moz.build | 1 - .../boot/public/nsISecurityWarningDialogs.idl | 32 --- security/manager/boot/src/moz.build | 1 - security/manager/boot/src/nsBOOTModule.cpp | 5 - .../boot/src/nsSecureBrowserUIImpl.cpp | 255 ------------------ .../manager/boot/src/nsSecureBrowserUIImpl.h | 26 -- .../boot/src/nsSecurityWarningDialogs.cpp | 162 ----------- .../boot/src/nsSecurityWarningDialogs.h | 45 ---- .../en-US/chrome/pipnss/security.properties | 7 - security/manager/locales/jar.mn | 1 - .../en-US/chrome/global/browser.properties | 4 + 13 files changed, 85 insertions(+), 558 deletions(-) delete mode 100644 security/manager/boot/public/nsISecurityWarningDialogs.idl delete mode 100644 security/manager/boot/src/nsSecurityWarningDialogs.cpp delete mode 100644 security/manager/boot/src/nsSecurityWarningDialogs.h delete mode 100644 security/manager/locales/en-US/chrome/pipnss/security.properties diff --git a/CLOBBER b/CLOBBER index e0e95faab14..bb1befee796 100644 --- a/CLOBBER +++ b/CLOBBER @@ -22,4 +22,4 @@ # changes to stick? As of bug 928195, this shouldn't be necessary! Please # don't change CLOBBER for WebIDL changes any more. -Bug 1109248 - This needed a CLOBBER on Windows and OSX. +bug 832837 removes nsISecurityWarningDialogs.idl, which requires a clobber according to bug 1114669 diff --git a/dom/html/HTMLFormElement.cpp b/dom/html/HTMLFormElement.cpp index 378a7f7e56e..61d5181ab7a 100644 --- a/dom/html/HTMLFormElement.cpp +++ b/dom/html/HTMLFormElement.cpp @@ -20,7 +20,6 @@ #include "nsPresContext.h" #include "nsIDocument.h" #include "nsIFormControlFrame.h" -#include "nsISecureBrowserUI.h" #include "nsError.h" #include "nsContentUtils.h" #include "nsInterfaceHashtable.h" @@ -33,6 +32,7 @@ #include "mozilla/BinarySearch.h" // form submission +#include "mozilla/Telemetry.h" #include "nsIFormSubmitObserver.h" #include "nsIObserverService.h" #include "nsICategoryManager.h" @@ -45,6 +45,9 @@ #include "nsIDocShell.h" #include "nsFormData.h" #include "nsFormSubmissionConstants.h" +#include "nsIPromptService.h" +#include "nsISecurityUITelemetry.h" +#include "nsIStringBundle.h" // radio buttons #include "mozilla/dom/HTMLInputElement.h" @@ -877,24 +880,79 @@ HTMLFormElement::NotifySubmitObservers(nsIURI* aActionURL, // sXBL/XBL2 issue nsCOMPtr window = OwnerDoc()->GetWindow(); - // Notify the secure browser UI, if any, that the form is being submitted. - nsCOMPtr docshell = OwnerDoc()->GetDocShell(); - if (docshell && !aEarlyNotify) { - nsCOMPtr secureUI; - docshell->GetSecurityUI(getter_AddRefs(secureUI)); - nsCOMPtr formSubmitObserver = - do_QueryInterface(secureUI); - if (formSubmitObserver) { - nsresult rv = formSubmitObserver->Notify(this, - window, - aActionURL, - aCancelSubmit); - NS_ENSURE_SUCCESS(rv, rv); - - if (*aCancelSubmit) { - return NS_OK; - } + nsCOMPtr principalURI; + nsresult rv = NodePrincipal()->GetURI(getter_AddRefs(principalURI)); + if (NS_FAILED(rv)) { + return rv; + } + bool formIsHTTPS; + rv = principalURI->SchemeIs("https", &formIsHTTPS); + if (NS_FAILED(rv)) { + return rv; + } + bool actionIsHTTPS; + rv = aActionURL->SchemeIs("https", &actionIsHTTPS); + if (NS_FAILED(rv)) { + return rv; + } + bool actionIsJS; + rv = aActionURL->SchemeIs("javascript", &actionIsJS); + if (NS_FAILED(rv)) { + return rv; + } + if (formIsHTTPS && !actionIsHTTPS && !actionIsJS && !aEarlyNotify) { + nsCOMPtr promptSvc = + do_GetService("@mozilla.org/embedcomp/prompt-service;1"); + if (!promptSvc) { + return NS_ERROR_FAILURE; } + nsCOMPtr stringBundle; + nsCOMPtr stringBundleService = + mozilla::services::GetStringBundleService(); + if (!stringBundleService) { + return NS_ERROR_FAILURE; + } + rv = stringBundleService->CreateBundle( + "chrome://global/locale/browser.properties", + getter_AddRefs(stringBundle)); + if (NS_FAILED(rv)) { + return rv; + } + nsAutoString title; + nsAutoString message; + nsAutoString cont; + stringBundle->GetStringFromName( + MOZ_UTF16("formPostSecureToInsecureWarning.title"), getter_Copies(title)); + stringBundle->GetStringFromName( + MOZ_UTF16("formPostSecureToInsecureWarning.message"), + getter_Copies(message)); + stringBundle->GetStringFromName( + MOZ_UTF16("formPostSecureToInsecureWarning.continue"), + getter_Copies(cont)); + int32_t buttonPressed; + bool checkState; // this is unused (ConfirmEx requires this parameter) + rv = promptSvc->ConfirmEx(window, title.get(), message.get(), + (nsIPromptService::BUTTON_TITLE_IS_STRING * + nsIPromptService::BUTTON_POS_0) + + (nsIPromptService::BUTTON_TITLE_CANCEL * + nsIPromptService::BUTTON_POS_1), + cont.get(), nullptr, nullptr, nullptr, + &checkState, &buttonPressed); + if (NS_FAILED(rv)) { + return rv; + } + *aCancelSubmit = (buttonPressed == 1); + uint32_t telemetryBucket = + nsISecurityUITelemetry::WARNING_CONFIRM_POST_TO_INSECURE_FROM_SECURE; + mozilla::Telemetry::Accumulate(mozilla::Telemetry::SECURITY_UI, + telemetryBucket); + // Return early if the submission is cancelled. + if (*aCancelSubmit) { + return NS_OK; + } + // The user opted to continue, so note that in the next telemetry bucket. + mozilla::Telemetry::Accumulate(mozilla::Telemetry::SECURITY_UI, + telemetryBucket + 1); } // Notify observers that the form is being submitted. @@ -904,10 +962,10 @@ HTMLFormElement::NotifySubmitObservers(nsIURI* aActionURL, return NS_ERROR_FAILURE; nsCOMPtr theEnum; - nsresult rv = service->EnumerateObservers(aEarlyNotify ? - NS_EARLYFORMSUBMIT_SUBJECT : - NS_FORMSUBMIT_SUBJECT, - getter_AddRefs(theEnum)); + rv = service->EnumerateObservers(aEarlyNotify ? + NS_EARLYFORMSUBMIT_SUBJECT : + NS_FORMSUBMIT_SUBJECT, + getter_AddRefs(theEnum)); NS_ENSURE_SUCCESS(rv, rv); if (theEnum) { diff --git a/security/manager/boot/public/moz.build b/security/manager/boot/public/moz.build index 5f841e1c1d1..b0879c6d99a 100644 --- a/security/manager/boot/public/moz.build +++ b/security/manager/boot/public/moz.build @@ -8,7 +8,6 @@ XPIDL_SOURCES += [ 'nsIBufEntropyCollector.idl', 'nsICertBlocklist.idl', 'nsISecurityUITelemetry.idl', - 'nsISecurityWarningDialogs.idl', 'nsISSLStatusProvider.idl', ] diff --git a/security/manager/boot/public/nsISecurityWarningDialogs.idl b/security/manager/boot/public/nsISecurityWarningDialogs.idl deleted file mode 100644 index d6c812abf43..00000000000 --- a/security/manager/boot/public/nsISecurityWarningDialogs.idl +++ /dev/null @@ -1,32 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "nsISupports.idl" - -interface nsIInterfaceRequestor; - -/** - * Functions that display warnings for transitions between secure - * and insecure pages, posts to insecure servers etc. - */ -[scriptable, uuid(a9561631-5964-4d3f-b372-9f23504054b1)] -interface nsISecurityWarningDialogs : nsISupports -{ - /** - * Inform the user: Although the currently displayed - * page was loaded using a secure connection, and the UI probably - * currently indicates a secure page, - * that information is being submitted to an insecure page. - * - * @param ctx A user interface context. - * - * @return true if the user confirms to submit. - */ - boolean confirmPostToInsecureFromSecure(in nsIInterfaceRequestor ctx); -}; - -%{C++ -#define NS_SECURITYWARNINGDIALOGS_CONTRACTID "@mozilla.org/nsSecurityWarningDialogs;1" -%} diff --git a/security/manager/boot/src/moz.build b/security/manager/boot/src/moz.build index 71078d16f0c..e181f84ff67 100644 --- a/security/manager/boot/src/moz.build +++ b/security/manager/boot/src/moz.build @@ -15,7 +15,6 @@ UNIFIED_SOURCES += [ 'nsEntropyCollector.cpp', 'nsSecureBrowserUIImpl.cpp', 'nsSecurityHeaderParser.cpp', - 'nsSecurityWarningDialogs.cpp', 'nsSiteSecurityService.cpp', 'PublicKeyPinningService.cpp', 'RootCertificateTelemetryUtils.cpp', diff --git a/security/manager/boot/src/nsBOOTModule.cpp b/security/manager/boot/src/nsBOOTModule.cpp index 62e1b6374a3..52f1a5d2893 100644 --- a/security/manager/boot/src/nsBOOTModule.cpp +++ b/security/manager/boot/src/nsBOOTModule.cpp @@ -8,24 +8,20 @@ #include "CertBlocklist.h" #include "nsEntropyCollector.h" #include "nsSecureBrowserUIImpl.h" -#include "nsSecurityWarningDialogs.h" #include "nsSiteSecurityService.h" NS_GENERIC_FACTORY_CONSTRUCTOR(nsEntropyCollector) NS_GENERIC_FACTORY_CONSTRUCTOR(nsSecureBrowserUIImpl) NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(CertBlocklist, Init) -NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsSecurityWarningDialogs, Init) NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsSiteSecurityService, Init) NS_DEFINE_NAMED_CID(NS_ENTROPYCOLLECTOR_CID); -NS_DEFINE_NAMED_CID(NS_SECURITYWARNINGDIALOGS_CID); NS_DEFINE_NAMED_CID(NS_SECURE_BROWSER_UI_CID); NS_DEFINE_NAMED_CID(NS_SITE_SECURITY_SERVICE_CID); NS_DEFINE_NAMED_CID(NS_CERT_BLOCKLIST_CID); static const mozilla::Module::CIDEntry kBOOTCIDs[] = { { &kNS_ENTROPYCOLLECTOR_CID, false, nullptr, nsEntropyCollectorConstructor }, - { &kNS_SECURITYWARNINGDIALOGS_CID, false, nullptr, nsSecurityWarningDialogsConstructor }, { &kNS_SECURE_BROWSER_UI_CID, false, nullptr, nsSecureBrowserUIImplConstructor }, { &kNS_SITE_SECURITY_SERVICE_CID, false, nullptr, nsSiteSecurityServiceConstructor }, { &kNS_CERT_BLOCKLIST_CID, false, nullptr, CertBlocklistConstructor}, @@ -34,7 +30,6 @@ static const mozilla::Module::CIDEntry kBOOTCIDs[] = { static const mozilla::Module::ContractIDEntry kBOOTContracts[] = { { NS_ENTROPYCOLLECTOR_CONTRACTID, &kNS_ENTROPYCOLLECTOR_CID }, - { NS_SECURITYWARNINGDIALOGS_CONTRACTID, &kNS_SECURITYWARNINGDIALOGS_CID }, { NS_SECURE_BROWSER_UI_CONTRACTID, &kNS_SECURE_BROWSER_UI_CID }, { NS_SSSERVICE_CONTRACTID, &kNS_SITE_SECURITY_SERVICE_CID }, { NS_CERTBLOCKLIST_CONTRACTID, &kNS_CERT_BLOCKLIST_CID }, diff --git a/security/manager/boot/src/nsSecureBrowserUIImpl.cpp b/security/manager/boot/src/nsSecureBrowserUIImpl.cpp index 132855f3ebb..a5b6bd9907d 100644 --- a/security/manager/boot/src/nsSecureBrowserUIImpl.cpp +++ b/security/manager/boot/src/nsSecureBrowserUIImpl.cpp @@ -9,17 +9,13 @@ #include "nsISecureBrowserUI.h" #include "nsSecureBrowserUIImpl.h" #include "nsCOMPtr.h" -#include "nsIInterfaceRequestor.h" -#include "nsIInterfaceRequestorUtils.h" #include "nsIServiceManager.h" #include "nsCURILoader.h" #include "nsIDocShell.h" #include "nsIDocShellTreeItem.h" #include "nsIDocument.h" -#include "nsIPrincipal.h" #include "nsIDOMElement.h" #include "nsPIDOMWindow.h" -#include "nsIContent.h" #include "nsIWebProgress.h" #include "nsIWebProgressListener.h" #include "nsIChannel.h" @@ -31,9 +27,6 @@ #include "nsISSLStatus.h" #include "nsIURI.h" #include "nsISecurityEventSink.h" -#include "nsIPrompt.h" -#include "nsIFormSubmitObserver.h" -#include "nsISecurityWarningDialogs.h" #include "nsISecurityInfoProvider.h" #include "imgIRequest.h" #include "nsThreadUtils.h" @@ -141,7 +134,6 @@ nsSecureBrowserUIImpl::~nsSecureBrowserUIImpl() NS_IMPL_ISUPPORTS(nsSecureBrowserUIImpl, nsISecureBrowserUI, nsIWebProgressListener, - nsIFormSubmitObserver, nsISupportsWeakReference, nsISSLStatusProvider) @@ -311,25 +303,6 @@ nsSecureBrowserUIImpl::SetDocShell(nsIDocShell *aDocShell) return rv; } -static nsresult IsChildOfDomWindow(nsIDOMWindow *parent, nsIDOMWindow *child, - bool* value) -{ - *value = false; - - if (parent == child) { - *value = true; - return NS_OK; - } - - nsCOMPtr childsParent; - child->GetParent(getter_AddRefs(childsParent)); - - if (childsParent && childsParent.get() != child) - IsChildOfDomWindow(parent, childsParent, value); - - return NS_OK; -} - static uint32_t GetSecurityStateFromSecurityInfoAndRequest(nsISupports* info, nsIRequest* request) { @@ -383,72 +356,6 @@ static uint32_t GetSecurityStateFromSecurityInfoAndRequest(nsISupports* info, } -NS_IMETHODIMP -nsSecureBrowserUIImpl::Notify(nsIDOMHTMLFormElement* aDOMForm, - nsIDOMWindow* aWindow, nsIURI* actionURL, - bool* cancelSubmit) -{ - // Return NS_OK unless we want to prevent this form from submitting. - *cancelSubmit = false; - if (!aWindow || !actionURL || !aDOMForm) - return NS_OK; - - nsCOMPtr formNode = do_QueryInterface(aDOMForm); - - nsCOMPtr document = formNode->GetComposedDoc(); - if (!document) return NS_OK; - - nsIPrincipal *principal = formNode->NodePrincipal(); - - if (!principal) - { - *cancelSubmit = true; - return NS_OK; - } - - nsCOMPtr formURL; - if (NS_FAILED(principal->GetURI(getter_AddRefs(formURL))) || - !formURL) - { - formURL = document->GetDocumentURI(); - } - - nsCOMPtr postingWindow = - do_QueryInterface(document->GetWindow()); - // We can't find this document's window, cancel it. - if (!postingWindow) - { - NS_WARNING("If you see this and can explain why it should be allowed, note in Bug 332324"); - *cancelSubmit = true; - return NS_OK; - } - - nsCOMPtr window; - { - ReentrantMonitorAutoEnter lock(mReentrantMonitor); - window = do_QueryReferent(mWindow); - - // The window was destroyed, so we assume no form was submitted within it. - if (!window) - return NS_OK; - } - - bool isChild; - IsChildOfDomWindow(window, postingWindow, &isChild); - - // This notify call is not for our window, ignore it. - if (!isChild) - return NS_OK; - - bool okayToPost; - nsresult res = CheckPost(formURL, actionURL, &okayToPost); - - if (NS_SUCCEEDED(res) && !okayToPost) - *cancelSubmit = true; - - return res; -} - // nsIWebProgressListener NS_IMETHODIMP nsSecureBrowserUIImpl::OnProgressChange(nsIWebProgress* aWebProgress, @@ -1508,165 +1415,3 @@ nsSecureBrowserUIImpl::GetSSLStatus(nsISSLStatus** _result) return NS_OK; } - -nsresult -nsSecureBrowserUIImpl::IsURLHTTPS(nsIURI* aURL, bool* value) -{ - *value = false; - - if (!aURL) - return NS_OK; - - return aURL->SchemeIs("https", value); -} - -nsresult -nsSecureBrowserUIImpl::IsURLJavaScript(nsIURI* aURL, bool* value) -{ - *value = false; - - if (!aURL) - return NS_OK; - - return aURL->SchemeIs("javascript", value); -} - -nsresult -nsSecureBrowserUIImpl::CheckPost(nsIURI *formURL, nsIURI *actionURL, bool *okayToPost) -{ - bool formSecure, actionSecure, actionJavaScript; - *okayToPost = true; - - nsresult rv = IsURLHTTPS(formURL, &formSecure); - if (NS_FAILED(rv)) - return rv; - - rv = IsURLHTTPS(actionURL, &actionSecure); - if (NS_FAILED(rv)) - return rv; - - rv = IsURLJavaScript(actionURL, &actionJavaScript); - if (NS_FAILED(rv)) - return rv; - - // If we are posting to a secure link, all is okay. - // It doesn't matter whether the currently viewed page is secure or not, - // because the data will be sent to a secure URL. - if (actionSecure) { - return NS_OK; - } - - // Action is a JavaScript call, not an actual post. That's okay too. - if (actionJavaScript) { - return NS_OK; - } - - // posting to insecure webpage from a secure webpage. - if (formSecure) { - *okayToPost = ConfirmPostToInsecureFromSecure(); - } - - return NS_OK; -} - -// -// Implementation of an nsIInterfaceRequestor for use -// as context for NSS calls -// -class nsUIContext : public nsIInterfaceRequestor -{ -public: - NS_DECL_ISUPPORTS - NS_DECL_NSIINTERFACEREQUESTOR - - explicit nsUIContext(nsIDOMWindow *window); - -protected: - virtual ~nsUIContext(); - -private: - nsCOMPtr mWindow; -}; - -NS_IMPL_ISUPPORTS(nsUIContext, nsIInterfaceRequestor) - -nsUIContext::nsUIContext(nsIDOMWindow *aWindow) -: mWindow(aWindow) -{ -} - -nsUIContext::~nsUIContext() -{ -} - -/* void getInterface (in nsIIDRef uuid, [iid_is (uuid), retval] out nsQIResult result); */ -NS_IMETHODIMP nsUIContext::GetInterface(const nsIID & uuid, void * *result) -{ - NS_ENSURE_TRUE(mWindow, NS_ERROR_FAILURE); - nsresult rv; - - if (uuid.Equals(NS_GET_IID(nsIPrompt))) { - nsCOMPtr window = do_QueryInterface(mWindow, &rv); - if (NS_FAILED(rv)) return rv; - - nsIPrompt *prompt; - - rv = window->GetPrompter(&prompt); - *result = prompt; - } else if (uuid.Equals(NS_GET_IID(nsIDOMWindow))) { - *result = mWindow; - NS_ADDREF ((nsISupports*) *result); - rv = NS_OK; - } else { - rv = NS_ERROR_NO_INTERFACE; - } - - return rv; -} - -bool -nsSecureBrowserUIImpl::GetNSSDialogs(nsCOMPtr & dialogs, - nsCOMPtr & ctx) -{ - if (!NS_IsMainThread()) { - NS_ERROR("nsSecureBrowserUIImpl::GetNSSDialogs called off the main thread"); - return false; - } - - dialogs = do_GetService(NS_SECURITYWARNINGDIALOGS_CONTRACTID); - if (!dialogs) - return false; - - nsCOMPtr window; - { - ReentrantMonitorAutoEnter lock(mReentrantMonitor); - window = do_QueryReferent(mWindow); - NS_ASSERTION(window, "Window has gone away?!"); - } - ctx = new nsUIContext(window); - - return true; -} - -/** - * ConfirmPostToInsecureFromSecure - returns true if - * the user approves the submit (or doesn't care). - * returns false on errors. - */ -bool nsSecureBrowserUIImpl:: -ConfirmPostToInsecureFromSecure() -{ - nsCOMPtr dialogs; - nsCOMPtr ctx; - - if (!GetNSSDialogs(dialogs, ctx)) { - return false; // Should this allow true for unimplemented? - } - - bool result; - - nsresult rv = dialogs->ConfirmPostToInsecureFromSecure(ctx, &result); - if (NS_FAILED(rv)) return false; - - return result; -} diff --git a/security/manager/boot/src/nsSecureBrowserUIImpl.h b/security/manager/boot/src/nsSecureBrowserUIImpl.h index 5f7b4edda05..4dd0792b6ed 100644 --- a/security/manager/boot/src/nsSecureBrowserUIImpl.h +++ b/security/manager/boot/src/nsSecureBrowserUIImpl.h @@ -14,12 +14,10 @@ #include "nsString.h" #include "nsIDOMElement.h" #include "nsIDOMWindow.h" -#include "nsIDOMHTMLFormElement.h" #include "nsISecureBrowserUI.h" #include "nsIDocShell.h" #include "nsIDocShellTreeItem.h" #include "nsIWebProgressListener.h" -#include "nsIFormSubmitObserver.h" #include "nsIURI.h" #include "nsISecurityEventSink.h" #include "nsWeakReference.h" @@ -29,10 +27,7 @@ #include "nsINetUtil.h" class nsISSLStatus; -class nsITransportSecurityInfo; -class nsISecurityWarningDialogs; class nsIChannel; -class nsIInterfaceRequestor; #define NS_SECURE_BROWSER_UI_CID \ { 0xcc75499a, 0x1dd1, 0x11b2, {0x8a, 0x82, 0xca, 0x41, 0x0a, 0xc9, 0x07, 0xb8}} @@ -40,7 +35,6 @@ class nsIInterfaceRequestor; class nsSecureBrowserUIImpl : public nsISecureBrowserUI, public nsIWebProgressListener, - public nsIFormSubmitObserver, public nsSupportsWeakReference, public nsISSLStatusProvider { @@ -51,14 +45,8 @@ public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIWEBPROGRESSLISTENER NS_DECL_NSISECUREBROWSERUI - NS_DECL_NSISSLSTATUSPROVIDER - NS_IMETHOD Notify(nsIDOMHTMLFormElement* formNode, nsIDOMWindow* window, - nsIURI *actionURL, bool* cancelSubmit) MOZ_OVERRIDE; - NS_IMETHOD NotifyInvalidSubmit(nsIDOMHTMLFormElement* formNode, - nsIArray* invalidElements) MOZ_OVERRIDE { return NS_OK; } - protected: virtual ~nsSecureBrowserUIImpl(); @@ -112,20 +100,6 @@ protected: nsCOMPtr mSSLStatus; nsCOMPtr mCurrentToplevelSecurityInfo; - nsresult CheckPost(nsIURI *formURI, nsIURI *actionURL, bool *okayToPost); - nsresult IsURLHTTPS(nsIURI* aURL, bool *value); - nsresult IsURLJavaScript(nsIURI* aURL, bool *value); - - bool ConfirmEnteringSecure(); - bool ConfirmEnteringWeak(); - bool ConfirmLeavingSecure(); - bool ConfirmMixedMode(); - bool ConfirmPostToInsecure(); - bool ConfirmPostToInsecureFromSecure(); - - bool GetNSSDialogs(nsCOMPtr & dialogs, - nsCOMPtr & window); - PLDHashTable mTransferringRequests; }; diff --git a/security/manager/boot/src/nsSecurityWarningDialogs.cpp b/security/manager/boot/src/nsSecurityWarningDialogs.cpp deleted file mode 100644 index 110afc7b92d..00000000000 --- a/security/manager/boot/src/nsSecurityWarningDialogs.cpp +++ /dev/null @@ -1,162 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "nsSecurityWarningDialogs.h" -#include "nsIComponentManager.h" -#include "nsIServiceManager.h" -#include "nsReadableUtils.h" -#include "nsString.h" -#include "nsIPrompt.h" -#include "nsIInterfaceRequestor.h" -#include "nsIInterfaceRequestorUtils.h" -#include "nsIPrefService.h" -#include "nsIPrefBranch.h" -#include "nsThreadUtils.h" - -#include "mozilla/Telemetry.h" -#include "nsISecurityUITelemetry.h" - -NS_IMPL_ISUPPORTS(nsSecurityWarningDialogs, nsISecurityWarningDialogs) - -#define STRING_BUNDLE_URL "chrome://pipnss/locale/security.properties" - -nsSecurityWarningDialogs::nsSecurityWarningDialogs() -{ -} - -nsSecurityWarningDialogs::~nsSecurityWarningDialogs() -{ -} - -nsresult -nsSecurityWarningDialogs::Init() -{ - nsresult rv; - - mPrefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); - if (NS_FAILED(rv)) return rv; - - nsCOMPtr service = - do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv); - if (NS_FAILED(rv)) return rv; - - rv = service->CreateBundle(STRING_BUNDLE_URL, - getter_AddRefs(mStringBundle)); - return rv; -} - -NS_IMETHODIMP -nsSecurityWarningDialogs::ConfirmPostToInsecureFromSecure(nsIInterfaceRequestor *ctx, bool* _result) -{ - nsresult rv; - - // The Telemetry clickthrough constant is 1 more than the constant for the dialog. - rv = ConfirmDialog(ctx, nullptr, // No preference for this one - it's too important - MOZ_UTF16("PostToInsecureFromSecureMessage"), - nullptr, - nsISecurityUITelemetry::WARNING_CONFIRM_POST_TO_INSECURE_FROM_SECURE, - _result); - - return rv; -} - -nsresult -nsSecurityWarningDialogs::ConfirmDialog(nsIInterfaceRequestor *ctx, const char *prefName, - const char16_t *messageName, - const char16_t *showAgainName, - const uint32_t aBucket, - bool* _result) -{ - nsresult rv; - - // Get user's preference for this alert - // prefName, showAgainName are null if there is no preference for this dialog - bool prefValue = true; - - if (prefName) { - rv = mPrefBranch->GetBoolPref(prefName, &prefValue); - if (NS_FAILED(rv)) prefValue = true; - } - - // Stop if confirm is not requested - if (!prefValue) { - *_result = true; - return NS_OK; - } - - MOZ_ASSERT(NS_IsMainThread()); - mozilla::Telemetry::Accumulate(mozilla::Telemetry::SECURITY_UI, aBucket); - // See AlertDialog() for a description of how showOnce works. - nsAutoCString showOncePref(prefName); - showOncePref += ".show_once"; - - bool showOnce = false; - mPrefBranch->GetBoolPref(showOncePref.get(), &showOnce); - - if (showOnce) - prefValue = false; - - // Get Prompt to use - nsCOMPtr prompt = do_GetInterface(ctx); - if (!prompt) return NS_ERROR_FAILURE; - - // Get messages strings from localization file - nsXPIDLString windowTitle, message, alertMe, cont; - - mStringBundle->GetStringFromName(MOZ_UTF16("Title"), - getter_Copies(windowTitle)); - mStringBundle->GetStringFromName(messageName, - getter_Copies(message)); - if (showAgainName) { - mStringBundle->GetStringFromName(showAgainName, - getter_Copies(alertMe)); - } - mStringBundle->GetStringFromName(MOZ_UTF16("Continue"), - getter_Copies(cont)); - // alertMe is allowed to be null - if (!windowTitle || !message || !cont) return NS_ERROR_FAILURE; - - // Replace # characters with newlines to lay out the dialog. - char16_t* msgchars = message.BeginWriting(); - - uint32_t i = 0; - for (i = 0; msgchars[i] != '\0'; i++) { - if (msgchars[i] == '#') { - msgchars[i] = '\n'; - } - } - - int32_t buttonPressed; - - rv = prompt->ConfirmEx(windowTitle, - message, - (nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_0) + - (nsIPrompt::BUTTON_TITLE_CANCEL * nsIPrompt::BUTTON_POS_1), - cont, - nullptr, - nullptr, - alertMe, - &prefValue, - &buttonPressed); - - if (NS_FAILED(rv)) return rv; - - *_result = (buttonPressed != 1); - if (*_result) { - // For confirmation dialogs, the clickthrough constant is 1 more - // than the constant for the dialog. - mozilla::Telemetry::Accumulate(mozilla::Telemetry::SECURITY_UI, aBucket + 1); - } - - if (!prefValue && prefName) { - mPrefBranch->SetBoolPref(prefName, false); - } else if (prefValue && showOnce) { - mPrefBranch->SetBoolPref(showOncePref.get(), false); - } - - return rv; -} - diff --git a/security/manager/boot/src/nsSecurityWarningDialogs.h b/security/manager/boot/src/nsSecurityWarningDialogs.h deleted file mode 100644 index 0a9313bae92..00000000000 --- a/security/manager/boot/src/nsSecurityWarningDialogs.h +++ /dev/null @@ -1,45 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef nsSecurityWarningDialogs_h -#define nsSecurityWarningDialogs_h - -#include "nsISecurityWarningDialogs.h" -#include "nsIPrefBranch.h" -#include "nsIStringBundle.h" -#include "nsCOMPtr.h" - -class nsSecurityWarningDialogs : public nsISecurityWarningDialogs -{ -public: - NS_DECL_THREADSAFE_ISUPPORTS - NS_DECL_NSISECURITYWARNINGDIALOGS - - nsSecurityWarningDialogs(); - - nsresult Init(); - -protected: - virtual ~nsSecurityWarningDialogs(); - - nsresult AlertDialog(nsIInterfaceRequestor *ctx, const char *prefName, - const char16_t *messageName, - const char16_t *showAgainName, - bool aAsync, const uint32_t aBucket); - nsresult ConfirmDialog(nsIInterfaceRequestor *ctx, const char *prefName, - const char16_t *messageName, - const char16_t *showAgainName, const uint32_t aBucket, - bool* _result); - nsCOMPtr mStringBundle; - nsCOMPtr mPrefBranch; -}; - -#define NS_SECURITYWARNINGDIALOGS_CID \ - { /* 8d995d4f-adcc-4159-b7f1-e94af72eeb88 */ \ - 0x8d995d4f, 0xadcc, 0x4159, \ - {0xb7, 0xf1, 0xe9, 0x4a, 0xf7, 0x2e, 0xeb, 0x88} } - -#endif diff --git a/security/manager/locales/en-US/chrome/pipnss/security.properties b/security/manager/locales/en-US/chrome/pipnss/security.properties deleted file mode 100644 index 50af92694e0..00000000000 --- a/security/manager/locales/en-US/chrome/pipnss/security.properties +++ /dev/null @@ -1,7 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -Title=Security Warning -PostToInsecureFromSecureMessage=Although this page is encrypted, the information you have entered is to be sent over an unencrypted connection and could easily be read by a third party.##Are you sure you want to continue sending this information?## -Continue=Continue diff --git a/security/manager/locales/jar.mn b/security/manager/locales/jar.mn index 66c1535f3b6..9b5c2da4461 100644 --- a/security/manager/locales/jar.mn +++ b/security/manager/locales/jar.mn @@ -9,7 +9,6 @@ % locale pippki @AB_CD@ %locale/@AB_CD@/pippki/ locale/@AB_CD@/pipnss/pipnss.properties (%chrome/pipnss/pipnss.properties) locale/@AB_CD@/pipnss/nsserrors.properties (%chrome/pipnss/nsserrors.properties) - locale/@AB_CD@/pipnss/security.properties (%chrome/pipnss/security.properties) locale/@AB_CD@/pippki/pippki.dtd (%chrome/pippki/pippki.dtd) locale/@AB_CD@/pippki/pippki.properties (%chrome/pippki/pippki.properties) locale/@AB_CD@/pippki/certManager.dtd (%chrome/pippki/certManager.dtd) diff --git a/toolkit/locales/en-US/chrome/global/browser.properties b/toolkit/locales/en-US/chrome/global/browser.properties index 0e5409fd1cc..2b315062918 100644 --- a/toolkit/locales/en-US/chrome/global/browser.properties +++ b/toolkit/locales/en-US/chrome/global/browser.properties @@ -8,3 +8,7 @@ browsewithcaret.checkLabel=Pressing F7 turns Caret Browsing on or off. This feat browsewithcaret.checkButtonLabel=Yes plainText.wordWrap=Wrap Long Lines + +formPostSecureToInsecureWarning.title = Security Warning +formPostSecureToInsecureWarning.message = The information you have entered on this page will be sent over an insecure connection and could be read by a third party.\n\nAre you sure you want to send this information? +formPostSecureToInsecureWarning.continue = Continue From 85cb670afc5f4ef6fe1ed50711765b6be4cbcae3 Mon Sep 17 00:00:00 2001 From: Kyle Machulis Date: Fri, 30 Jan 2015 10:41:46 -0800 Subject: [PATCH 026/101] Bug 1127948 - Remove FAIL expectation for user timing web platform idl tests; a=TEST-ONLY --- testing/web-platform/meta/user-timing/idlharness.html.ini | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 testing/web-platform/meta/user-timing/idlharness.html.ini diff --git a/testing/web-platform/meta/user-timing/idlharness.html.ini b/testing/web-platform/meta/user-timing/idlharness.html.ini deleted file mode 100644 index a10b1d0a558..00000000000 --- a/testing/web-platform/meta/user-timing/idlharness.html.ini +++ /dev/null @@ -1,8 +0,0 @@ -[idlharness.html] - type: testharness - [PerformanceMark interface: existence and properties of interface object] - expected: FAIL - - [PerformanceMeasure interface: existence and properties of interface object] - expected: FAIL - From af7d48d91b3177e5e57aabe565c67866cecb8d09 Mon Sep 17 00:00:00 2001 From: Trevor Saunders Date: Mon, 26 Jan 2015 14:41:41 -0500 Subject: [PATCH 027/101] bug 1124821 - make shutdown of attached documents more robust r=smaug --- accessible/ipc/DocAccessibleParent.cpp | 16 +++++++++++++--- accessible/ipc/ProxyAccessible.cpp | 2 +- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/accessible/ipc/DocAccessibleParent.cpp b/accessible/ipc/DocAccessibleParent.cpp index 966e44a043b..38b41357e9d 100644 --- a/accessible/ipc/DocAccessibleParent.cpp +++ b/accessible/ipc/DocAccessibleParent.cpp @@ -15,6 +15,9 @@ namespace a11y { bool DocAccessibleParent::RecvShowEvent(const ShowEventData& aData) { + if (mShutdown) + return true; + if (aData.NewTree().IsEmpty()) { NS_ERROR("no children being added"); return false; @@ -95,6 +98,9 @@ DocAccessibleParent::AddSubtree(ProxyAccessible* aParent, bool DocAccessibleParent::RecvHideEvent(const uint64_t& aRootID) { + if (mShutdown) + return true; + ProxyEntry* rootEntry = mAccessibles.GetEntry(aRootID); if (!rootEntry) { NS_ERROR("invalid root being removed!"); @@ -151,17 +157,21 @@ PLDHashOperator DocAccessibleParent::ShutdownAccessibles(ProxyEntry* entry, void*) { ProxyDestroyed(entry->mProxy); - return PL_DHASH_NEXT; + return PL_DHASH_REMOVE; } void DocAccessibleParent::Destroy() { - MOZ_ASSERT(mChildDocs.IsEmpty(), - "why weren't the child docs destroyed already?"); + NS_ASSERTION(mChildDocs.IsEmpty(), + "why weren't the child docs destroyed already?"); MOZ_ASSERT(!mShutdown); mShutdown = true; + uint32_t childDocCount = mChildDocs.Length(); + for (uint32_t i = childDocCount - 1; i < childDocCount; i--) + mChildDocs[i]->Destroy(); + mAccessibles.EnumerateEntries(ShutdownAccessibles, nullptr); ProxyDestroyed(this); mParentDoc ? mParentDoc->RemoveChildDoc(this) diff --git a/accessible/ipc/ProxyAccessible.cpp b/accessible/ipc/ProxyAccessible.cpp index 06afa1085dd..b42c16c894a 100644 --- a/accessible/ipc/ProxyAccessible.cpp +++ b/accessible/ipc/ProxyAccessible.cpp @@ -15,7 +15,7 @@ namespace a11y { void ProxyAccessible::Shutdown() { - MOZ_ASSERT(!mOuterDoc); + NS_ASSERTION(!mOuterDoc, "Why do we still have a child doc?"); // XXX Ideally this wouldn't be necessary, but it seems OuterDoc accessibles // can be destroyed before the doc they own. From 81d99912efb36757baba70955ef54c5222001bdc Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Fri, 30 Jan 2015 13:54:43 -0500 Subject: [PATCH 028/101] Bug 1127206. Don't try to do binding UnwrapArgImpl on worker threads. It can't do anything useful there. r=peterv --- dom/base/File.cpp | 5 +- dom/bindings/BindingUtils.cpp | 4 ++ dom/bindings/test/mochitest.ini | 1 + dom/bindings/test/test_worker_UnwrapArg.html | 58 ++++++++++++++++++++ 4 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 dom/bindings/test/test_worker_UnwrapArg.html diff --git a/dom/base/File.cpp b/dom/base/File.cpp index 45f76a7c426..c1b35c50997 100644 --- a/dom/base/File.cpp +++ b/dom/base/File.cpp @@ -626,7 +626,7 @@ File::Constructor(const GlobalObject& aGlobal, const ChromeFilePropertyBag& aBag, ErrorResult& aRv) { - if (!nsContentUtils::IsCallerChrome()) { + if (!nsContentUtils::ThreadsafeIsCallerChrome()) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } @@ -648,6 +648,7 @@ File::Constructor(const GlobalObject& aGlobal, const ChromeFilePropertyBag& aBag, ErrorResult& aRv) { + MOZ_ASSERT(NS_IsMainThread()); if (!nsContentUtils::IsCallerChrome()) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; @@ -672,7 +673,7 @@ File::Constructor(const GlobalObject& aGlobal, const ChromeFilePropertyBag& aBag, ErrorResult& aRv) { - if (!nsContentUtils::IsCallerChrome()) { + if (!nsContentUtils::ThreadsafeIsCallerChrome()) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } diff --git a/dom/bindings/BindingUtils.cpp b/dom/bindings/BindingUtils.cpp index c4a21f28a00..97d9c8dd4ac 100644 --- a/dom/bindings/BindingUtils.cpp +++ b/dom/bindings/BindingUtils.cpp @@ -2726,6 +2726,10 @@ UnwrapArgImpl(JS::Handle src, const nsIID &iid, void **ppArg) { + if (!NS_IsMainThread()) { + return NS_ERROR_NOT_AVAILABLE; + } + nsISupports *iface = xpc::UnwrapReflectorToISupports(src); if (iface) { if (NS_FAILED(iface->QueryInterface(iid, ppArg))) { diff --git a/dom/bindings/test/mochitest.ini b/dom/bindings/test/mochitest.ini index a2fb55a137a..1b6e49aae9f 100644 --- a/dom/bindings/test/mochitest.ini +++ b/dom/bindings/test/mochitest.ini @@ -57,3 +57,4 @@ skip-if = debug == false skip-if = debug == false [test_promise_rejections_from_jsimplemented.html] skip-if = debug == false +[test_worker_UnwrapArg.html] diff --git a/dom/bindings/test/test_worker_UnwrapArg.html b/dom/bindings/test/test_worker_UnwrapArg.html new file mode 100644 index 00000000000..1331a83f415 --- /dev/null +++ b/dom/bindings/test/test_worker_UnwrapArg.html @@ -0,0 +1,58 @@ + + + + + + Test for Bug 1127206 + + + + + +Mozilla Bug 1127206 +

+ +
+
+ + From 512a816c97804abccceebea7b2444fc73174e9e2 Mon Sep 17 00:00:00 2001 From: Andrew McCreight Date: Fri, 30 Jan 2015 10:55:11 -0800 Subject: [PATCH 029/101] Bug 1124261, part 1 - Remove some null checks from XBL. r=smaug --- dom/xbl/nsBindingManager.cpp | 3 +- dom/xbl/nsXBLContentSink.cpp | 54 +++++++++++-------------------- dom/xbl/nsXBLProtoImpl.cpp | 2 -- dom/xbl/nsXBLPrototypeBinding.cpp | 1 - dom/xbl/nsXBLService.cpp | 8 ++--- dom/xbl/nsXBLWindowKeyHandler.cpp | 3 -- 6 files changed, 21 insertions(+), 50 deletions(-) diff --git a/dom/xbl/nsBindingManager.cpp b/dom/xbl/nsBindingManager.cpp index 8c54a60afba..07e2a592560 100644 --- a/dom/xbl/nsBindingManager.cpp +++ b/dom/xbl/nsBindingManager.cpp @@ -344,8 +344,7 @@ nsBindingManager::RemoveFromAttachedQueue(nsXBLBinding* aBinding) nsresult nsBindingManager::AddToAttachedQueue(nsXBLBinding* aBinding) { - if (!mAttachedStack.AppendElement(aBinding)) - return NS_ERROR_OUT_OF_MEMORY; + mAttachedStack.AppendElement(aBinding); // If we're in the middle of processing our queue already, don't // bother posting the event. diff --git a/dom/xbl/nsXBLContentSink.cpp b/dom/xbl/nsXBLContentSink.cpp index 03934610a63..422b5680c6a 100644 --- a/dom/xbl/nsXBLContentSink.cpp +++ b/dom/xbl/nsXBLContentSink.cpp @@ -39,7 +39,6 @@ NS_NewXBLContentSink(nsIXMLContentSink** aResult, NS_ENSURE_ARG_POINTER(aResult); nsXBLContentSink* it = new nsXBLContentSink(); - NS_ENSURE_TRUE(it, NS_ERROR_OUT_OF_MEMORY); nsCOMPtr kungFuDeathGrip = it; nsresult rv = it->Init(aDoc, aURI, aContainer); @@ -461,11 +460,9 @@ nsXBLContentSink::OnOpenContainer(const char16_t **aAtts, } nsXBLProtoImplAnonymousMethod* newMethod = new nsXBLProtoImplAnonymousMethod(name.get()); - if (newMethod) { - newMethod->SetLineNumber(aLineNumber); - mBinding->SetConstructor(newMethod); - AddMember(newMethod); - } + newMethod->SetLineNumber(aLineNumber); + mBinding->SetConstructor(newMethod); + AddMember(newMethod); } else if (aTagName == nsGkAtoms::destructor) { ENSURE_XBL_STATE(mState == eXBL_InImplementation && @@ -481,11 +478,9 @@ nsXBLContentSink::OnOpenContainer(const char16_t **aAtts, } nsXBLProtoImplAnonymousMethod* newMethod = new nsXBLProtoImplAnonymousMethod(name.get()); - if (newMethod) { - newMethod->SetLineNumber(aLineNumber); - mBinding->SetDestructor(newMethod); - AddMember(newMethod); - } + newMethod->SetLineNumber(aLineNumber); + mBinding->SetDestructor(newMethod); + AddMember(newMethod); } else if (aTagName == nsGkAtoms::field) { ENSURE_XBL_STATE(mState == eXBL_InImplementation && @@ -551,9 +546,7 @@ nsXBLContentSink::ConstructBinding(uint32_t aLineNumber) // performs this check. if (!cid.IsEmpty()) { mBinding = new nsXBLPrototypeBinding(); - if (!mBinding) - return NS_ERROR_OUT_OF_MEMORY; - + rv = mBinding->Init(cid, mDocInfo, binding, !mFoundFirstBinding); if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(mDocInfo->SetPrototypeBinding(cid, mBinding))) { @@ -675,22 +668,17 @@ nsXBLContentSink::ConstructHandler(const char16_t **aAtts, uint32_t aLineNumber) clickcount, group, preventdefault, allowuntrusted, mBinding, aLineNumber); - if (newHandler) { - // Add this handler to our chain of handlers. - if (mHandler) { - // Already have a chain. Just append to the end. - mHandler->SetNextHandler(newHandler); - } - else { - // We're the first handler in the chain. - mBinding->SetPrototypeHandlers(newHandler); - } - // Adjust our mHandler pointer to point to the new last handler in the - // chain. - mHandler = newHandler; + // Add this handler to our chain of handlers. + if (mHandler) { + // Already have a chain. Just append to the end. + mHandler->SetNextHandler(newHandler); } else { - mState = eXBL_Error; + // We're the first handler in the chain. + mBinding->SetPrototypeHandlers(newHandler); } + // Adjust our mHandler pointer to point to the new last handler in the + // chain. + mHandler = newHandler; } void @@ -773,10 +761,8 @@ nsXBLContentSink::ConstructField(const char16_t **aAtts, uint32_t aLineNumber) // All of our pointers are now filled in. Construct our field with all of // these parameters. mField = new nsXBLProtoImplField(name, readonly); - if (mField) { - mField->SetLineNumber(aLineNumber); - AddField(mField); - } + mField->SetLineNumber(aLineNumber); + AddField(mField); } } @@ -882,8 +868,6 @@ nsXBLContentSink::CreateElement(const char16_t** aAtts, uint32_t aAttsCount, *aAppendContent = true; nsRefPtr prototype = new nsXULPrototypeElement(); - if (!prototype) - return NS_ERROR_OUT_OF_MEMORY; prototype->mNodeInfo = aNodeInfo; @@ -919,8 +903,6 @@ nsXBLContentSink::AddAttributesToXULPrototype(const char16_t **aAtts, nsXULPrototypeAttribute* attrs = nullptr; if (aAttsCount > 0) { attrs = new nsXULPrototypeAttribute[aAttsCount]; - if (!attrs) - return NS_ERROR_OUT_OF_MEMORY; } aElement->mAttributes = attrs; diff --git a/dom/xbl/nsXBLProtoImpl.cpp b/dom/xbl/nsXBLProtoImpl.cpp index acb0a8587b6..d9510d15aa7 100644 --- a/dom/xbl/nsXBLProtoImpl.cpp +++ b/dom/xbl/nsXBLProtoImpl.cpp @@ -520,8 +520,6 @@ NS_NewXBLProtoImpl(nsXBLPrototypeBinding* aBinding, nsXBLProtoImpl** aResult) { nsXBLProtoImpl* impl = new nsXBLProtoImpl(); - if (!impl) - return NS_ERROR_OUT_OF_MEMORY; if (aClassName) impl->mClassName.AssignWithConversion(aClassName); else diff --git a/dom/xbl/nsXBLPrototypeBinding.cpp b/dom/xbl/nsXBLPrototypeBinding.cpp index 7ffb2ee5433..90f34aa3738 100644 --- a/dom/xbl/nsXBLPrototypeBinding.cpp +++ b/dom/xbl/nsXBLPrototypeBinding.cpp @@ -1264,7 +1264,6 @@ nsXBLPrototypeBinding::ReadContentNode(nsIObjectInputStream* aStream, nsIURI* documentURI = aDocument->GetDocumentURI(); nsRefPtr prototype = new nsXULPrototypeElement(); - NS_ENSURE_TRUE(prototype, NS_ERROR_OUT_OF_MEMORY); prototype->mNodeInfo = nodeInfo; diff --git a/dom/xbl/nsXBLService.cpp b/dom/xbl/nsXBLService.cpp index a05f090ce19..6d952bcddf6 100644 --- a/dom/xbl/nsXBLService.cpp +++ b/dom/xbl/nsXBLService.cpp @@ -764,12 +764,10 @@ nsXBLService::GetBinding(nsIContent* aBoundElement, nsIURI* aURI, return NS_ERROR_FAILURE; } - NS_ENSURE_TRUE(aDontExtendURIs.AppendElement(protoBinding->BindingURI()), - NS_ERROR_OUT_OF_MEMORY); + aDontExtendURIs.AppendElement(protoBinding->BindingURI()); nsCOMPtr altBindingURI = protoBinding->AlternateBindingURI(); if (altBindingURI) { - NS_ENSURE_TRUE(aDontExtendURIs.AppendElement(altBindingURI), - NS_ERROR_OUT_OF_MEMORY); + aDontExtendURIs.AppendElement(altBindingURI); } // Our prototype binding must have all its resources loaded. @@ -831,7 +829,6 @@ nsXBLService::GetBinding(nsIContent* aBoundElement, nsIURI* aURI, if (!aPeekOnly) { // Make a new binding nsXBLBinding *newBinding = new nsXBLBinding(protoBinding); - NS_ENSURE_TRUE(newBinding, NS_ERROR_OUT_OF_MEMORY); if (baseBinding) { if (!baseProto) { @@ -1108,7 +1105,6 @@ nsXBLService::FetchBindingDocument(nsIContent* aBoundElement, nsIDocument* aBoun // We can be asynchronous nsXBLStreamListener* xblListener = new nsXBLStreamListener(aBoundDocument, xblSink, doc); - NS_ENSURE_TRUE(xblListener,NS_ERROR_OUT_OF_MEMORY); // Add ourselves to the list of loading docs. nsBindingManager *bindingManager; diff --git a/dom/xbl/nsXBLWindowKeyHandler.cpp b/dom/xbl/nsXBLWindowKeyHandler.cpp index 56693e4bb5c..069f22fbcac 100644 --- a/dom/xbl/nsXBLWindowKeyHandler.cpp +++ b/dom/xbl/nsXBLWindowKeyHandler.cpp @@ -214,9 +214,6 @@ BuildHandlerChain(nsIContent* aContent, nsXBLPrototypeHandler** aResult) nsXBLPrototypeHandler* handler = new nsXBLPrototypeHandler(key); - if (!handler) - return; - handler->SetNextHandler(*aResult); *aResult = handler; } From e6150b649ae82d5b0e31144fff808e471f83e038 Mon Sep 17 00:00:00 2001 From: Andrew McCreight Date: Fri, 30 Jan 2015 10:55:11 -0800 Subject: [PATCH 030/101] Bug 1124261, part 2 - Make NS_NewXBLEventHandler infallible. r=smaug --- dom/xbl/nsXBLEventHandler.cpp | 18 +++++++----------- dom/xbl/nsXBLEventHandler.h | 5 ++--- dom/xbl/nsXBLPrototypeHandler.h | 3 +-- 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/dom/xbl/nsXBLEventHandler.cpp b/dom/xbl/nsXBLEventHandler.cpp index 46cb8743b82..8d10a772bfd 100644 --- a/dom/xbl/nsXBLEventHandler.cpp +++ b/dom/xbl/nsXBLEventHandler.cpp @@ -160,30 +160,26 @@ nsXBLKeyEventHandler::HandleEvent(nsIDOMEvent* aEvent) /////////////////////////////////////////////////////////////////////////////////// -nsresult +already_AddRefed NS_NewXBLEventHandler(nsXBLPrototypeHandler* aHandler, - nsIAtom* aEventType, - nsXBLEventHandler** aResult) + nsIAtom* aEventType) { + nsRefPtr handler; + switch (nsContentUtils::GetEventClassID(nsDependentAtomString(aEventType))) { case eDragEventClass: case eMouseEventClass: case eMouseScrollEventClass: case eWheelEventClass: case eSimpleGestureEventClass: - *aResult = new nsXBLMouseEventHandler(aHandler); + handler = new nsXBLMouseEventHandler(aHandler); break; default: - *aResult = new nsXBLEventHandler(aHandler); + handler = new nsXBLEventHandler(aHandler); break; } - if (!*aResult) - return NS_ERROR_OUT_OF_MEMORY; - - NS_ADDREF(*aResult); - - return NS_OK; + return handler.forget(); } nsresult diff --git a/dom/xbl/nsXBLEventHandler.h b/dom/xbl/nsXBLEventHandler.h index 0ae2ffebc86..7e64e44af58 100644 --- a/dom/xbl/nsXBLEventHandler.h +++ b/dom/xbl/nsXBLEventHandler.h @@ -113,10 +113,9 @@ private: bool mUsingContentXBLScope; }; -nsresult +already_AddRefed NS_NewXBLEventHandler(nsXBLPrototypeHandler* aHandler, - nsIAtom* aEventType, - nsXBLEventHandler** aResult); + nsIAtom* aEventType); nsresult NS_NewXBLKeyEventHandler(nsIAtom* aEventType, uint8_t aPhase, diff --git a/dom/xbl/nsXBLPrototypeHandler.h b/dom/xbl/nsXBLPrototypeHandler.h index d293c237a5c..c1545681852 100644 --- a/dom/xbl/nsXBLPrototypeHandler.h +++ b/dom/xbl/nsXBLPrototypeHandler.h @@ -132,8 +132,7 @@ public: nsXBLEventHandler* GetEventHandler() { if (!mHandler) { - NS_NewXBLEventHandler(this, mEventName, getter_AddRefs(mHandler)); - // XXX Need to signal out of memory? + mHandler = NS_NewXBLEventHandler(this, mEventName); } return mHandler; From 553c1b7ff111b192b5adadce1bffbb361b9842a6 Mon Sep 17 00:00:00 2001 From: Andrew McCreight Date: Fri, 30 Jan 2015 10:55:11 -0800 Subject: [PATCH 031/101] Bug 1124261, part 3 - Inline NS_NewXBLKeyEventHandler. r=smaug --- dom/xbl/nsXBLEventHandler.cpp | 14 -------------- dom/xbl/nsXBLEventHandler.h | 4 ---- dom/xbl/nsXBLPrototypeBinding.cpp | 8 +++----- 3 files changed, 3 insertions(+), 23 deletions(-) diff --git a/dom/xbl/nsXBLEventHandler.cpp b/dom/xbl/nsXBLEventHandler.cpp index 8d10a772bfd..3eb96eced07 100644 --- a/dom/xbl/nsXBLEventHandler.cpp +++ b/dom/xbl/nsXBLEventHandler.cpp @@ -181,17 +181,3 @@ NS_NewXBLEventHandler(nsXBLPrototypeHandler* aHandler, return handler.forget(); } - -nsresult -NS_NewXBLKeyEventHandler(nsIAtom* aEventType, uint8_t aPhase, uint8_t aType, - nsXBLKeyEventHandler** aResult) -{ - *aResult = new nsXBLKeyEventHandler(aEventType, aPhase, aType); - - if (!*aResult) - return NS_ERROR_OUT_OF_MEMORY; - - NS_ADDREF(*aResult); - - return NS_OK; -} diff --git a/dom/xbl/nsXBLEventHandler.h b/dom/xbl/nsXBLEventHandler.h index 7e64e44af58..ab418b179f4 100644 --- a/dom/xbl/nsXBLEventHandler.h +++ b/dom/xbl/nsXBLEventHandler.h @@ -117,8 +117,4 @@ already_AddRefed NS_NewXBLEventHandler(nsXBLPrototypeHandler* aHandler, nsIAtom* aEventType); -nsresult -NS_NewXBLKeyEventHandler(nsIAtom* aEventType, uint8_t aPhase, - uint8_t aType, nsXBLKeyEventHandler** aResult); - #endif diff --git a/dom/xbl/nsXBLPrototypeBinding.cpp b/dom/xbl/nsXBLPrototypeBinding.cpp index 90f34aa3738..a66a1b19d34 100644 --- a/dom/xbl/nsXBLPrototypeBinding.cpp +++ b/dom/xbl/nsXBLPrototypeBinding.cpp @@ -807,11 +807,9 @@ nsXBLPrototypeBinding::CreateKeyHandlers() } if (i == count) { - nsRefPtr newHandler; - NS_NewXBLKeyEventHandler(eventAtom, phase, type, - getter_AddRefs(newHandler)); - if (newHandler) - mKeyHandlers.AppendObject(newHandler); + nsRefPtr newHandler = + new nsXBLKeyEventHandler(eventAtom, phase, type); + mKeyHandlers.AppendObject(newHandler); handler = newHandler; } From 166baa3b8f27bea74e018b348b55362563ab9db4 Mon Sep 17 00:00:00 2001 From: Andrew McCreight Date: Fri, 30 Jan 2015 10:59:02 -0800 Subject: [PATCH 032/101] Bug 1127013 - Use unix file endings in mochitest-e10s-utils. r=jmaher --- testing/mochitest/mochitest-e10s-utils.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/testing/mochitest/mochitest-e10s-utils.js b/testing/mochitest/mochitest-e10s-utils.js index 55fe1e2453c..5897c65426a 100644 --- a/testing/mochitest/mochitest-e10s-utils.js +++ b/testing/mochitest/mochitest-e10s-utils.js @@ -1,11 +1,11 @@ -// Utilities for running tests in an e10s environment. - -function e10s_init() { - // Listen for an 'oop-browser-crashed' event and log it so people analysing - // test logs have a clue about what is going on. - window.addEventListener("oop-browser-crashed", (event) => { - let uri = event.target.currentURI; - Cu.reportError("remote browser crashed while on " + - (uri ? uri.spec : "") + "\n"); - }, true); -} +// Utilities for running tests in an e10s environment. + +function e10s_init() { + // Listen for an 'oop-browser-crashed' event and log it so people analysing + // test logs have a clue about what is going on. + window.addEventListener("oop-browser-crashed", (event) => { + let uri = event.target.currentURI; + Cu.reportError("remote browser crashed while on " + + (uri ? uri.spec : "") + "\n"); + }, true); +} From 10994231d0e6c75279b2eadf7ea160103191d378 Mon Sep 17 00:00:00 2001 From: Andrew McCreight Date: Fri, 30 Jan 2015 10:59:49 -0800 Subject: [PATCH 033/101] Bug 1123854 - Remove some out of memory checks in caps/. r=jst --- caps/nsNullPrincipal.cpp | 1 - caps/nsScriptSecurityManager.cpp | 3 --- 2 files changed, 4 deletions(-) diff --git a/caps/nsNullPrincipal.cpp b/caps/nsNullPrincipal.cpp index b84a5ff4970..ab1b58dae3c 100644 --- a/caps/nsNullPrincipal.cpp +++ b/caps/nsNullPrincipal.cpp @@ -116,7 +116,6 @@ nsNullPrincipal::Init(uint32_t aAppId, bool aInMozBrowser) } mURI = new nsNullPrincipalURI(str); - NS_ENSURE_TRUE(mURI, NS_ERROR_OUT_OF_MEMORY); return NS_OK; } diff --git a/caps/nsScriptSecurityManager.cpp b/caps/nsScriptSecurityManager.cpp index 68c3d25b1e8..369c56cefd1 100644 --- a/caps/nsScriptSecurityManager.cpp +++ b/caps/nsScriptSecurityManager.cpp @@ -986,8 +986,6 @@ nsScriptSecurityManager::CreateCodebasePrincipal(nsIURI* aURI, uint32_t aAppId, } nsRefPtr codebase = new nsPrincipal(); - if (!codebase) - return NS_ERROR_OUT_OF_MEMORY; nsresult rv = codebase->Init(aURI, aAppId, aInMozBrowser); if (NS_FAILED(rv)) @@ -1269,7 +1267,6 @@ nsresult nsScriptSecurityManager::Init() // Create our system principal singleton nsRefPtr system = new nsSystemPrincipal(); - NS_ENSURE_TRUE(system, NS_ERROR_OUT_OF_MEMORY); mSystemPrincipal = system; From 76028359a53f6248a666e5797030ffba638ca97f Mon Sep 17 00:00:00 2001 From: Steve Fink Date: Fri, 30 Jan 2015 11:06:02 -0800 Subject: [PATCH 034/101] Bug 1127581 - Do not clear slices when failing due to OOM, r=terrence --- js/src/gc/Statistics.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/js/src/gc/Statistics.cpp b/js/src/gc/Statistics.cpp index 65f4cc8c2c0..7fb6b0a4692 100644 --- a/js/src/gc/Statistics.cpp +++ b/js/src/gc/Statistics.cpp @@ -983,7 +983,6 @@ Statistics::beginSlice(const ZoneGCStats &zoneStats, JSGCInvocationKind gckind, if (!slices.append(data)) { // OOM testing fails if we CrashAtUnhandlableOOM here. abortSlices = true; - slices.clear(); return; } From 5def7f4a1a28dcfd93b2b67fe28e9666d6ee745a Mon Sep 17 00:00:00 2001 From: Ryan VanderMeulen Date: Fri, 30 Jan 2015 14:16:40 -0500 Subject: [PATCH 035/101] Backed out 4 changesets (bug 782751, bug 1127948) for Gaia UI test failures. Backed out changeset 4d8735e0cc30 (bug 1127948) Backed out changeset de498dd4e303 (bug 782751) Backed out changeset fe9436f5474c (bug 782751) Backed out changeset fff8f6c32743 (bug 782751) --- dom/base/PerformanceEntry.cpp | 8 +- dom/base/PerformanceEntry.h | 4 +- dom/base/PerformanceMark.cpp | 27 -- dom/base/PerformanceMark.h | 36 --- dom/base/PerformanceMeasure.cpp | 30 -- dom/base/PerformanceMeasure.h | 44 --- dom/base/PerformanceResourceTiming.cpp | 5 +- dom/base/PerformanceResourceTiming.h | 3 +- dom/base/moz.build | 4 - dom/base/nsDOMAttributeMap.cpp | 1 - dom/base/nsPerformance.cpp | 277 ++-------------- dom/base/nsPerformance.h | 17 +- dom/base/test/mochitest.ini | 2 - .../test/test_performance_user_timing.html | 306 ------------------ .../mochitest/general/test_interfaces.html | 4 - dom/webidl/Performance.webidl | 13 - dom/webidl/PerformanceMark.webidl | 12 - dom/webidl/PerformanceMeasure.webidl | 12 - dom/webidl/moz.build | 2 - modules/libpref/init/all.js | 3 - .../meta/user-timing/idlharness.html.ini | 62 ++++ .../test_user_timing_clear_marks.html.ini | 5 + .../test_user_timing_clear_measures.html.ini | 5 + .../test_user_timing_entry_type.html.ini | 3 + .../test_user_timing_exists.html.ini | 14 + .../test_user_timing_mark.html.ini | 5 +- ...ion_when_invoke_without_parameter.html.ini | 8 + .../test_user_timing_mark_exceptions.html.ini | 5 + .../test_user_timing_measure.html.ini | 5 + ...st_user_timing_measure_exceptions.html.ini | 5 + ..._timing_measure_navigation_timing.html.ini | 5 + 31 files changed, 147 insertions(+), 785 deletions(-) delete mode 100644 dom/base/PerformanceMark.cpp delete mode 100644 dom/base/PerformanceMark.h delete mode 100644 dom/base/PerformanceMeasure.cpp delete mode 100644 dom/base/PerformanceMeasure.h delete mode 100644 dom/base/test/test_performance_user_timing.html delete mode 100644 dom/webidl/PerformanceMark.webidl delete mode 100644 dom/webidl/PerformanceMeasure.webidl create mode 100644 testing/web-platform/meta/user-timing/idlharness.html.ini create mode 100644 testing/web-platform/meta/user-timing/test_user_timing_clear_marks.html.ini create mode 100644 testing/web-platform/meta/user-timing/test_user_timing_clear_measures.html.ini create mode 100644 testing/web-platform/meta/user-timing/test_user_timing_entry_type.html.ini create mode 100644 testing/web-platform/meta/user-timing/test_user_timing_exists.html.ini create mode 100644 testing/web-platform/meta/user-timing/test_user_timing_mark_and_measure_exception_when_invoke_without_parameter.html.ini create mode 100644 testing/web-platform/meta/user-timing/test_user_timing_mark_exceptions.html.ini create mode 100644 testing/web-platform/meta/user-timing/test_user_timing_measure.html.ini create mode 100644 testing/web-platform/meta/user-timing/test_user_timing_measure_exceptions.html.ini create mode 100644 testing/web-platform/meta/user-timing/test_user_timing_measure_navigation_timing.html.ini diff --git a/dom/base/PerformanceEntry.cpp b/dom/base/PerformanceEntry.cpp index 1472d59fe70..f52a0e79766 100644 --- a/dom/base/PerformanceEntry.cpp +++ b/dom/base/PerformanceEntry.cpp @@ -19,12 +19,8 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PerformanceEntry) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END -PerformanceEntry::PerformanceEntry(nsPerformance* aPerformance, - const nsAString& aName, - const nsAString& aEntryType) -: mPerformance(aPerformance), - mName(aName), - mEntryType(aEntryType) +PerformanceEntry::PerformanceEntry(nsPerformance* aPerformance) +: mPerformance(aPerformance) { MOZ_ASSERT(aPerformance, "Parent performance object should be provided"); } diff --git a/dom/base/PerformanceEntry.h b/dom/base/PerformanceEntry.h index edffe8b30db..d55530b3dc8 100644 --- a/dom/base/PerformanceEntry.h +++ b/dom/base/PerformanceEntry.h @@ -20,9 +20,7 @@ protected: virtual ~PerformanceEntry(); public: - PerformanceEntry(nsPerformance* aPerformance, - const nsAString& aName, - const nsAString& aEntryType); + explicit PerformanceEntry(nsPerformance* aPerformance); NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(PerformanceEntry) diff --git a/dom/base/PerformanceMark.cpp b/dom/base/PerformanceMark.cpp deleted file mode 100644 index 67952b256bf..00000000000 --- a/dom/base/PerformanceMark.cpp +++ /dev/null @@ -1,27 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "PerformanceMark.h" -#include "mozilla/dom/PerformanceMarkBinding.h" - -using namespace mozilla::dom; - -PerformanceMark::PerformanceMark(nsPerformance* aPerformance, - const nsAString& aName) -: PerformanceEntry(aPerformance, aName, NS_LITERAL_STRING("mark")) -{ - MOZ_ASSERT(aPerformance, "Parent performance object should be provided"); - mStartTime = aPerformance->GetDOMTiming()->TimeStampToDOMHighRes(mozilla::TimeStamp::Now()); -} - -PerformanceMark::~PerformanceMark() -{ -} - -JSObject* -PerformanceMark::WrapObject(JSContext* aCx) -{ - return PerformanceMarkBinding::Wrap(aCx, this); -} diff --git a/dom/base/PerformanceMark.h b/dom/base/PerformanceMark.h deleted file mode 100644 index e20a0338a82..00000000000 --- a/dom/base/PerformanceMark.h +++ /dev/null @@ -1,36 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef mozilla_dom_performancemark_h___ -#define mozilla_dom_performancemark_h___ - -#include "mozilla/dom/PerformanceEntry.h" - -namespace mozilla { -namespace dom { - -// http://www.w3.org/TR/user-timing/#performancemark -class PerformanceMark MOZ_FINAL : public PerformanceEntry -{ -public: - PerformanceMark(nsPerformance* aPerformance, - const nsAString& aName); - - virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE; - - virtual DOMHighResTimeStamp StartTime() const MOZ_OVERRIDE - { - return mStartTime; - } - -protected: - virtual ~PerformanceMark(); - DOMHighResTimeStamp mStartTime; -}; - -} // namespace dom -} // namespace mozilla - -#endif /* mozilla_dom_performancemark_h___ */ diff --git a/dom/base/PerformanceMeasure.cpp b/dom/base/PerformanceMeasure.cpp deleted file mode 100644 index 90d59b89849..00000000000 --- a/dom/base/PerformanceMeasure.cpp +++ /dev/null @@ -1,30 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "PerformanceMeasure.h" -#include "mozilla/dom/PerformanceMeasureBinding.h" - -using namespace mozilla::dom; - -PerformanceMeasure::PerformanceMeasure(nsPerformance* aPerformance, - const nsAString& aName, - DOMHighResTimeStamp aStartTime, - DOMHighResTimeStamp aEndTime) -: PerformanceEntry(aPerformance, aName, NS_LITERAL_STRING("measure")), - mStartTime(aStartTime), - mDuration(aEndTime - aStartTime) -{ - MOZ_ASSERT(aPerformance, "Parent performance object should be provided"); -} - -PerformanceMeasure::~PerformanceMeasure() -{ -} - -JSObject* -PerformanceMeasure::WrapObject(JSContext* aCx) -{ - return PerformanceMeasureBinding::Wrap(aCx, this); -} diff --git a/dom/base/PerformanceMeasure.h b/dom/base/PerformanceMeasure.h deleted file mode 100644 index 1cc44746439..00000000000 --- a/dom/base/PerformanceMeasure.h +++ /dev/null @@ -1,44 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef mozilla_dom_performancemeasure_h___ -#define mozilla_dom_performancemeasure_h___ - -#include "mozilla/dom/PerformanceEntry.h" - -namespace mozilla { -namespace dom { - -// http://www.w3.org/TR/user-timing/#performancemeasure -class PerformanceMeasure MOZ_FINAL : public PerformanceEntry -{ -public: - PerformanceMeasure(nsPerformance* aPerformance, - const nsAString& aName, - DOMHighResTimeStamp aStartTime, - DOMHighResTimeStamp aEndTime); - - virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE; - - virtual DOMHighResTimeStamp StartTime() const MOZ_OVERRIDE - { - return mStartTime; - } - - virtual DOMHighResTimeStamp Duration() const MOZ_OVERRIDE - { - return mDuration; - } - -protected: - virtual ~PerformanceMeasure(); - DOMHighResTimeStamp mStartTime; - DOMHighResTimeStamp mDuration; -}; - -} // namespace dom -} // namespace mozilla - -#endif /* mozilla_dom_performancemeasure_h___ */ diff --git a/dom/base/PerformanceResourceTiming.cpp b/dom/base/PerformanceResourceTiming.cpp index 646358e491e..74b836986ec 100644 --- a/dom/base/PerformanceResourceTiming.cpp +++ b/dom/base/PerformanceResourceTiming.cpp @@ -23,9 +23,8 @@ NS_IMPL_ADDREF_INHERITED(PerformanceResourceTiming, PerformanceEntry) NS_IMPL_RELEASE_INHERITED(PerformanceResourceTiming, PerformanceEntry) PerformanceResourceTiming::PerformanceResourceTiming(nsPerformanceTiming* aPerformanceTiming, - nsPerformance* aPerformance, - const nsAString& aName) -: PerformanceEntry(aPerformance, aName, NS_LITERAL_STRING("resource")), + nsPerformance* aPerformance) +: PerformanceEntry(aPerformance), mTiming(aPerformanceTiming) { MOZ_ASSERT(aPerformance, "Parent performance object should be provided"); diff --git a/dom/base/PerformanceResourceTiming.h b/dom/base/PerformanceResourceTiming.h index 5d682898f03..ad04e8b321b 100644 --- a/dom/base/PerformanceResourceTiming.h +++ b/dom/base/PerformanceResourceTiming.h @@ -28,8 +28,7 @@ public: PerformanceEntry) PerformanceResourceTiming(nsPerformanceTiming* aPerformanceTiming, - nsPerformance* aPerformance, - const nsAString& aName); + nsPerformance* aPerformance); virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE; diff --git a/dom/base/moz.build b/dom/base/moz.build index 5fcd8d5020a..7c22caf0011 100644 --- a/dom/base/moz.build +++ b/dom/base/moz.build @@ -181,8 +181,6 @@ EXPORTS.mozilla.dom += [ 'NodeInfoInlines.h', 'NodeIterator.h', 'PerformanceEntry.h', - 'PerformanceMark.h', - 'PerformanceMeasure.h', 'PerformanceResourceTiming.h', 'ResponsiveImageSelector.h', 'ScreenOrientation.h', @@ -317,8 +315,6 @@ UNIFIED_SOURCES += [ 'nsXMLHttpRequest.cpp', 'nsXMLNameSpaceMap.cpp', 'PerformanceEntry.cpp', - 'PerformanceMark.cpp', - 'PerformanceMeasure.cpp', 'PerformanceResourceTiming.cpp', 'ResponsiveImageSelector.cpp', 'ScriptSettings.cpp', diff --git a/dom/base/nsDOMAttributeMap.cpp b/dom/base/nsDOMAttributeMap.cpp index 54a472d2c1b..53ab47b46ca 100644 --- a/dom/base/nsDOMAttributeMap.cpp +++ b/dom/base/nsDOMAttributeMap.cpp @@ -14,7 +14,6 @@ #include "mozilla/dom/Attr.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/NamedNodeMapBinding.h" -#include "mozilla/dom/NodeInfoInlines.h" #include "nsAttrName.h" #include "nsContentUtils.h" #include "nsError.h" diff --git a/dom/base/nsPerformance.cpp b/dom/base/nsPerformance.cpp index 57f005cad5b..fb3612a9d8d 100644 --- a/dom/base/nsPerformance.cpp +++ b/dom/base/nsPerformance.cpp @@ -11,21 +11,17 @@ #include "nsContentUtils.h" #include "nsIScriptSecurityManager.h" #include "nsIDOMWindow.h" -#include "nsILoadInfo.h" #include "nsIURI.h" -#include "nsThreadUtils.h" #include "PerformanceEntry.h" -#include "PerformanceMark.h" -#include "PerformanceMeasure.h" #include "PerformanceResourceTiming.h" -#include "mozilla/ErrorResult.h" #include "mozilla/dom/PerformanceBinding.h" #include "mozilla/dom/PerformanceTimingBinding.h" #include "mozilla/dom/PerformanceNavigationBinding.h" #include "mozilla/TimeStamp.h" +#include "nsThreadUtils.h" +#include "nsILoadInfo.h" using namespace mozilla; -using namespace mozilla::dom; NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsPerformanceTiming, mPerformance) @@ -329,7 +325,7 @@ nsPerformanceTiming::ResponseStart() DOMHighResTimeStamp nsPerformanceTiming::ResponseEndHighRes() { - if (!IsInitialized()) { + if (!nsContentUtils::IsPerformanceTimingEnabled() || !IsInitialized()) { return mZeroTime; } if (mResponseEnd.IsNull() || @@ -354,7 +350,7 @@ nsPerformanceTiming::IsInitialized() const JSObject* nsPerformanceTiming::WrapObject(JSContext *cx) { - return PerformanceTimingBinding::Wrap(cx, this); + return dom::PerformanceTimingBinding::Wrap(cx, this); } @@ -376,7 +372,7 @@ nsPerformanceNavigation::~nsPerformanceNavigation() JSObject* nsPerformanceNavigation::WrapObject(JSContext *cx) { - return PerformanceNavigationBinding::Wrap(cx, this); + return dom::PerformanceNavigationBinding::Wrap(cx, this); } @@ -453,13 +449,13 @@ nsPerformance::Navigation() DOMHighResTimeStamp nsPerformance::Now() { - return GetDOMTiming()->TimeStampToDOMHighRes(TimeStamp::Now()); + return GetDOMTiming()->TimeStampToDOMHighRes(mozilla::TimeStamp::Now()); } JSObject* nsPerformance::WrapObject(JSContext *cx) { - return PerformanceBinding::Wrap(cx, this); + return dom::PerformanceBinding::Wrap(cx, this); } void @@ -487,7 +483,7 @@ nsPerformance::GetEntriesByType(const nsAString& entryType, void nsPerformance::GetEntriesByName(const nsAString& name, - const Optional& entryType, + const mozilla::dom::Optional& entryType, nsTArray >& retval) { MOZ_ASSERT(NS_IsMainThread()); @@ -503,28 +499,11 @@ nsPerformance::GetEntriesByName(const nsAString& name, } } -void -nsPerformance::ClearEntries(const Optional& aEntryName, - const nsAString& aEntryType) -{ - for (uint32_t i = 0; i < mEntries.Length();) { - if ((!aEntryName.WasPassed() || - mEntries[i]->GetName().Equals(aEntryName.Value())) && - (aEntryType.IsEmpty() || - mEntries[i]->GetEntryType().Equals(aEntryType))) { - mEntries.RemoveElementAt(i); - } else { - ++i; - } - } -} - void nsPerformance::ClearResourceTimings() { MOZ_ASSERT(NS_IsMainThread()); - ClearEntries(Optional(), - NS_LITERAL_STRING("resource")); + mEntries.Clear(); } void @@ -550,7 +529,6 @@ nsPerformance::AddEntry(nsIHttpChannel* channel, // Don't add the entry if the buffer is full if (mEntries.Length() >= mPrimaryBufferSize) { - NS_WARNING("Performance Entry buffer size maximum reached!"); return; } @@ -580,16 +558,24 @@ nsPerformance::AddEntry(nsIHttpChannel* channel, // The PerformanceResourceTiming object will use the nsPerformanceTiming // object to get all the required timings. - nsRefPtr performanceEntry = - new PerformanceResourceTiming(performanceTiming, this, entryName); + nsRefPtr performanceEntry = + new dom::PerformanceResourceTiming(performanceTiming, this); + performanceEntry->SetName(entryName); + performanceEntry->SetEntryType(NS_LITERAL_STRING("resource")); // If the initiator type had no valid value, then set it to the default // ("other") value. if (initiatorType.IsEmpty()) { initiatorType = NS_LITERAL_STRING("other"); } performanceEntry->SetInitiatorType(initiatorType); - InsertPerformanceEntry(performanceEntry); + + mEntries.InsertElementSorted(performanceEntry, + PerformanceEntryComparator()); + if (mEntries.Length() >= mPrimaryBufferSize) { + // call onresourcetimingbufferfull + DispatchBufferFullEvent(); + } } } @@ -612,226 +598,3 @@ nsPerformance::PerformanceEntryComparator::LessThan( "Trying to compare null performance entries"); return aElem1->StartTime() < aElem2->StartTime(); } - -void -nsPerformance::InsertPerformanceEntry(PerformanceEntry* aEntry) -{ - MOZ_ASSERT(aEntry); - MOZ_ASSERT(mEntries.Length() < mPrimaryBufferSize); - if (mEntries.Length() == mPrimaryBufferSize) { - NS_WARNING("Performance Entry buffer size maximum reached!"); - return; - } - mEntries.InsertElementSorted(aEntry, - PerformanceEntryComparator()); - if (mEntries.Length() == mPrimaryBufferSize) { - // call onresourcetimingbufferfull - DispatchBufferFullEvent(); - } -} - -void -nsPerformance::Mark(const nsAString& aName, ErrorResult& aRv) -{ - MOZ_ASSERT(NS_IsMainThread()); - // Don't add the entry if the buffer is full - if (mEntries.Length() >= mPrimaryBufferSize) { - NS_WARNING("Performance Entry buffer size maximum reached!"); - return; - } - if (IsPerformanceTimingAttribute(aName)) { - aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); - return; - } - nsRefPtr performanceMark = - new PerformanceMark(this, aName); - InsertPerformanceEntry(performanceMark); -} - -void -nsPerformance::ClearMarks(const Optional& aName) -{ - MOZ_ASSERT(NS_IsMainThread()); - ClearEntries(aName, NS_LITERAL_STRING("mark")); -} - -DOMHighResTimeStamp -nsPerformance::ResolveTimestampFromName(const nsAString& aName, - ErrorResult& aRv) -{ - nsAutoTArray, 1> arr; - DOMHighResTimeStamp ts; - Optional typeParam; - nsAutoString str; - str.AssignLiteral("mark"); - typeParam = &str; - GetEntriesByName(aName, typeParam, arr); - if (!arr.IsEmpty()) { - return arr.LastElement()->StartTime(); - } - if (!IsPerformanceTimingAttribute(aName)) { - aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); - return 0; - } - ts = GetPerformanceTimingFromString(aName); - if (!ts) { - aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); - return 0; - } - return ConvertDOMMilliSecToHighRes(ts); -} - -void -nsPerformance::Measure(const nsAString& aName, - const Optional& aStartMark, - const Optional& aEndMark, - ErrorResult& aRv) -{ - MOZ_ASSERT(NS_IsMainThread()); - // Don't add the entry if the buffer is full - if (mEntries.Length() >= mPrimaryBufferSize) { - NS_WARNING("Performance Entry buffer size maximum reached!"); - return; - } - DOMHighResTimeStamp startTime; - DOMHighResTimeStamp endTime; - - if (IsPerformanceTimingAttribute(aName)) { - aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); - return; - } - - if (aStartMark.WasPassed()) { - startTime = ResolveTimestampFromName(aStartMark.Value(), aRv); - if (NS_WARN_IF(aRv.Failed())) { - return; - } - } else { - // Navigation start is used in this case, but since DOMHighResTimeStamp is - // in relation to navigation start, this will be zero if a name is not - // passed. - startTime = 0; - } - if (aEndMark.WasPassed()) { - endTime = ResolveTimestampFromName(aEndMark.Value(), aRv); - if (NS_WARN_IF(aRv.Failed())) { - return; - } - } else { - endTime = Now(); - } - nsRefPtr performanceMeasure = - new PerformanceMeasure(this, aName, startTime, endTime); - InsertPerformanceEntry(performanceMeasure); -} - -void -nsPerformance::ClearMeasures(const Optional& aName) -{ - MOZ_ASSERT(NS_IsMainThread()); - ClearEntries(aName, NS_LITERAL_STRING("measure")); -} - -DOMHighResTimeStamp -nsPerformance::ConvertDOMMilliSecToHighRes(DOMTimeMilliSec aTime) { - // If the time we're trying to convert is equal to zero, it hasn't been set - // yet so just return 0. - if (aTime == 0) { - return 0; - } - return aTime - GetDOMTiming()->GetNavigationStart(); -} - -// To be removed once bug 1124165 lands -bool -nsPerformance::IsPerformanceTimingAttribute(const nsAString& aName) -{ - // Note that toJSON is added to this list due to bug 1047848 - static const char* attributes[] = - {"navigationStart", "unloadEventStart", "unloadEventEnd", "redirectStart", - "redirectEnd", "fetchStart", "domainLookupStart", "domainLookupEnd", - "connectStart", "connectEnd", "requestStart", "responseStart", - "responseEnd", "domLoading", "domInteractive", "domContentLoadedEventStart", - "domContentLoadedEventEnd", "domComplete", "loadEventStart", - "loadEventEnd", nullptr}; - - for (uint32_t i = 0; attributes[i]; ++i) { - if (aName.EqualsASCII(attributes[i])) { - return true; - } - } - return false; -} - -DOMTimeMilliSec -nsPerformance::GetPerformanceTimingFromString(const nsAString& aProperty) -{ - if (!IsPerformanceTimingAttribute(aProperty)) { - return 0; - } - if (aProperty.EqualsLiteral("navigationStart")) { - // DOMHighResTimeStamp is in relation to navigationStart, so this will be - // zero. - return GetDOMTiming()->GetNavigationStart(); - } - if (aProperty.EqualsLiteral("unloadEventStart")) { - return GetDOMTiming()->GetUnloadEventStart(); - } - if (aProperty.EqualsLiteral("unloadEventEnd")) { - return GetDOMTiming()->GetUnloadEventEnd(); - } - if (aProperty.EqualsLiteral("redirectStart")) { - return Timing()->RedirectStart(); - } - if (aProperty.EqualsLiteral("redirectEnd")) { - return Timing()->RedirectEnd(); - } - if (aProperty.EqualsLiteral("fetchStart")) { - return Timing()->FetchStart(); - } - if (aProperty.EqualsLiteral("domainLookupStart")) { - return Timing()->DomainLookupStart(); - } - if (aProperty.EqualsLiteral("domainLookupEnd")) { - return Timing()->DomainLookupEnd(); - } - if (aProperty.EqualsLiteral("connectStart")) { - return Timing()->ConnectStart(); - } - if (aProperty.EqualsLiteral("connectEnd")) { - return Timing()->ConnectEnd(); - } - if (aProperty.EqualsLiteral("requestStart")) { - return Timing()->RequestStart(); - } - if (aProperty.EqualsLiteral("responseStart")) { - return Timing()->ResponseStart(); - } - if (aProperty.EqualsLiteral("responseEnd")) { - return Timing()->ResponseEnd(); - } - if (aProperty.EqualsLiteral("domLoading")) { - return GetDOMTiming()->GetDomLoading(); - } - if (aProperty.EqualsLiteral("domInteractive")) { - return GetDOMTiming()->GetDomInteractive(); - } - if (aProperty.EqualsLiteral("domContentLoadedEventStart")) { - return GetDOMTiming()->GetDomContentLoadedEventStart(); - } - if (aProperty.EqualsLiteral("domContentLoadedEventEnd")) { - return GetDOMTiming()->GetDomContentLoadedEventEnd(); - } - if (aProperty.EqualsLiteral("domComplete")) { - return GetDOMTiming()->GetDomComplete(); - } - if (aProperty.EqualsLiteral("loadEventStart")) { - return GetDOMTiming()->GetLoadEventStart(); - } - if (aProperty.EqualsLiteral("loadEventEnd")) { - return GetDOMTiming()->GetLoadEventEnd(); - } - MOZ_CRASH("IsPerformanceTimingAttribute and GetPerformanceTimingFromString are out of sync"); - return 0; -} - diff --git a/dom/base/nsPerformance.h b/dom/base/nsPerformance.h index 22b01538e35..57fbbf11c58 100644 --- a/dom/base/nsPerformance.h +++ b/dom/base/nsPerformance.h @@ -21,7 +21,6 @@ class nsPerformance; class nsIHttpChannel; namespace mozilla { -class ErrorResult; namespace dom { class PerformanceEntry; } @@ -336,26 +335,12 @@ public: nsITimedChannel* timedChannel); void ClearResourceTimings(); void SetResourceTimingBufferSize(uint64_t maxSize); - void Mark(const nsAString& aName, mozilla::ErrorResult& aRv); - void ClearMarks(const mozilla::dom::Optional& aName); - void Measure(const nsAString& aName, - const mozilla::dom::Optional& aStartMark, - const mozilla::dom::Optional& aEndMark, - mozilla::ErrorResult& aRv); - void ClearMeasures(const mozilla::dom::Optional& aName); - IMPL_EVENT_HANDLER(resourcetimingbufferfull) private: ~nsPerformance(); - bool IsPerformanceTimingAttribute(const nsAString& aName); - DOMHighResTimeStamp ResolveTimestampFromName(const nsAString& aName, mozilla::ErrorResult& aRv); - DOMTimeMilliSec GetPerformanceTimingFromString(const nsAString& aTimingName); - DOMHighResTimeStamp ConvertDOMMilliSecToHighRes(const DOMTimeMilliSec aTime); void DispatchBufferFullEvent(); - void InsertPerformanceEntry(PerformanceEntry* aEntry); - void ClearEntries(const mozilla::dom::Optional& aEntryName, - const nsAString& aEntryType); + nsCOMPtr mWindow; nsRefPtr mDOMTiming; nsCOMPtr mChannel; diff --git a/dom/base/test/mochitest.ini b/dom/base/test/mochitest.ini index 5f23800a45c..8f21b6383fe 100644 --- a/dom/base/test/mochitest.ini +++ b/dom/base/test/mochitest.ini @@ -770,5 +770,3 @@ skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' skip-if = buildapp == 'b2g' || toolkit == 'android' || e10s [test_window_define_nonconfigurable.html] skip-if = true # bug 1107443 - code for newly-added test was disabled -[test_performance_user_timing.html] - diff --git a/dom/base/test/test_performance_user_timing.html b/dom/base/test/test_performance_user_timing.html deleted file mode 100644 index c2fa73cc3c3..00000000000 --- a/dom/base/test/test_performance_user_timing.html +++ /dev/null @@ -1,306 +0,0 @@ - - - - - Test for Bug 782751 - - - - - Mozilla Bug 782751 - User Timing API -
-
-
-            
-        
- - diff --git a/dom/tests/mochitest/general/test_interfaces.html b/dom/tests/mochitest/general/test_interfaces.html index fac8b0093b7..8c0ffb7fd48 100644 --- a/dom/tests/mochitest/general/test_interfaces.html +++ b/dom/tests/mochitest/general/test_interfaces.html @@ -859,10 +859,6 @@ var interfaceNamesInGlobalScope = "Performance", // IMPORTANT: Do not change this list without review from a DOM peer! "PerformanceEntry", -// IMPORTANT: Do not change this list without review from a DOM peer! - "PerformanceMark", -// IMPORTANT: Do not change this list without review from a DOM peer! - "PerformanceMeasure", // IMPORTANT: Do not change this list without review from a DOM peer! "PerformanceNavigation", // IMPORTANT: Do not change this list without review from a DOM peer! diff --git a/dom/webidl/Performance.webidl b/dom/webidl/Performance.webidl index e6274f8c732..c17e1840d38 100644 --- a/dom/webidl/Performance.webidl +++ b/dom/webidl/Performance.webidl @@ -51,16 +51,3 @@ partial interface Performance { [Pref="dom.enable_resource_timing"] attribute EventHandler onresourcetimingbufferfull; }; - -// http://www.w3.org/TR/user-timing/ -[Exposed=Window] -partial interface Performance { - [Pref="dom.enable_user_timing", Throws] - void mark(DOMString markName); - [Pref="dom.enable_user_timing"] - void clearMarks(optional DOMString markName); - [Pref="dom.enable_user_timing", Throws] - void measure(DOMString measureName, optional DOMString startMark, optional DOMString endMark); - [Pref="dom.enable_user_timing"] - void clearMeasures(optional DOMString measureName); -}; diff --git a/dom/webidl/PerformanceMark.webidl b/dom/webidl/PerformanceMark.webidl deleted file mode 100644 index fb20c334a0b..00000000000 --- a/dom/webidl/PerformanceMark.webidl +++ /dev/null @@ -1,12 +0,0 @@ -/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. - * - * The origin of this IDL file is - * http://www.w3.org/TR/user-timing/#performancemark - */ - -interface PerformanceMark : PerformanceEntry -{ -}; diff --git a/dom/webidl/PerformanceMeasure.webidl b/dom/webidl/PerformanceMeasure.webidl deleted file mode 100644 index e203c6d066d..00000000000 --- a/dom/webidl/PerformanceMeasure.webidl +++ /dev/null @@ -1,12 +0,0 @@ -/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. - * - * The origin of this IDL file is - * http://www.w3.org/TR/user-timing/#performancemeasure - */ - -interface PerformanceMeasure : PerformanceEntry -{ -}; diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build index de17f8544e8..ff8758687fe 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -320,8 +320,6 @@ WEBIDL_FILES = [ 'ParentNode.webidl', 'Performance.webidl', 'PerformanceEntry.webidl', - 'PerformanceMark.webidl', - 'PerformanceMeasure.webidl', 'PerformanceNavigation.webidl', 'PerformanceResourceTiming.webidl', 'PerformanceTiming.webidl', diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 7b54ed8295d..3c75ea9407c 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -144,9 +144,6 @@ pref("dom.enable_performance", true); // Whether resource timing will be gathered and returned by performance.GetEntries* pref("dom.enable_resource_timing", true); -// Enable high-resolution timing markers for users -pref("dom.enable_user_timing", true); - // Whether the Gamepad API is enabled pref("dom.gamepad.enabled", true); #ifdef RELEASE_BUILD diff --git a/testing/web-platform/meta/user-timing/idlharness.html.ini b/testing/web-platform/meta/user-timing/idlharness.html.ini new file mode 100644 index 00000000000..202a51440ac --- /dev/null +++ b/testing/web-platform/meta/user-timing/idlharness.html.ini @@ -0,0 +1,62 @@ +[idlharness.html] + type: testharness + [Performance interface: operation mark(DOMString)] + expected: FAIL + + [Performance interface: operation clearMarks(DOMString)] + expected: FAIL + + [Performance interface: operation measure(DOMString,DOMString,DOMString)] + expected: FAIL + + [Performance interface: operation clearMeasures(DOMString)] + expected: FAIL + + [Performance interface: window.performance must inherit property "mark" with the proper type (0)] + expected: FAIL + + [Performance interface: calling mark(DOMString) on window.performance with too few arguments must throw TypeError] + expected: FAIL + + [Performance interface: window.performance must inherit property "clearMarks" with the proper type (1)] + expected: FAIL + + [Performance interface: calling clearMarks(DOMString) on window.performance with too few arguments must throw TypeError] + expected: FAIL + + [Performance interface: window.performance must inherit property "measure" with the proper type (2)] + expected: FAIL + + [Performance interface: calling measure(DOMString,DOMString,DOMString) on window.performance with too few arguments must throw TypeError] + expected: FAIL + + [Performance interface: window.performance must inherit property "clearMeasures" with the proper type (3)] + expected: FAIL + + [Performance interface: calling clearMeasures(DOMString) on window.performance with too few arguments must throw TypeError] + expected: FAIL + + [PerformanceMark interface: existence and properties of interface object] + expected: FAIL + + [PerformanceMark interface object length] + expected: FAIL + + [PerformanceMark interface: existence and properties of interface prototype object] + expected: FAIL + + [PerformanceMark interface: existence and properties of interface prototype object\'s "constructor" property] + expected: FAIL + + [PerformanceMeasure interface: existence and properties of interface object] + expected: FAIL + + [PerformanceMeasure interface object length] + expected: FAIL + + [PerformanceMeasure interface: existence and properties of interface prototype object] + expected: FAIL + + [PerformanceMeasure interface: existence and properties of interface prototype object\'s "constructor" property] + expected: FAIL + diff --git a/testing/web-platform/meta/user-timing/test_user_timing_clear_marks.html.ini b/testing/web-platform/meta/user-timing/test_user_timing_clear_marks.html.ini new file mode 100644 index 00000000000..8c63f905e65 --- /dev/null +++ b/testing/web-platform/meta/user-timing/test_user_timing_clear_marks.html.ini @@ -0,0 +1,5 @@ +[test_user_timing_clear_marks.html] + type: testharness + [The User Timing and Performance Timeline interfaces, which are required for this test, are defined.] + expected: FAIL + diff --git a/testing/web-platform/meta/user-timing/test_user_timing_clear_measures.html.ini b/testing/web-platform/meta/user-timing/test_user_timing_clear_measures.html.ini new file mode 100644 index 00000000000..ab517286b42 --- /dev/null +++ b/testing/web-platform/meta/user-timing/test_user_timing_clear_measures.html.ini @@ -0,0 +1,5 @@ +[test_user_timing_clear_measures.html] + type: testharness + [The User Timing and Performance Timeline interfaces, which are required for this test, are defined.] + expected: FAIL + diff --git a/testing/web-platform/meta/user-timing/test_user_timing_entry_type.html.ini b/testing/web-platform/meta/user-timing/test_user_timing_entry_type.html.ini new file mode 100644 index 00000000000..6ae5df1ab23 --- /dev/null +++ b/testing/web-platform/meta/user-timing/test_user_timing_entry_type.html.ini @@ -0,0 +1,3 @@ +[test_user_timing_entry_type.html] + type: testharness + expected: ERROR diff --git a/testing/web-platform/meta/user-timing/test_user_timing_exists.html.ini b/testing/web-platform/meta/user-timing/test_user_timing_exists.html.ini new file mode 100644 index 00000000000..f9e7b3cafd0 --- /dev/null +++ b/testing/web-platform/meta/user-timing/test_user_timing_exists.html.ini @@ -0,0 +1,14 @@ +[test_user_timing_exists.html] + type: testharness + [window.performance.mark is defined.] + expected: FAIL + + [window.performance.clearMarks is defined.] + expected: FAIL + + [window.performance.measure is defined.] + expected: FAIL + + [window.performance.clearMeasures is defined.] + expected: FAIL + diff --git a/testing/web-platform/meta/user-timing/test_user_timing_mark.html.ini b/testing/web-platform/meta/user-timing/test_user_timing_mark.html.ini index 1e2d28d7d48..b7ff61f6a8f 100644 --- a/testing/web-platform/meta/user-timing/test_user_timing_mark.html.ini +++ b/testing/web-platform/meta/user-timing/test_user_timing_mark.html.ini @@ -1,4 +1,5 @@ [test_user_timing_mark.html] type: testharness - disabled: - if (os == "win"): https://bugzilla.mozilla.org/show_bug.cgi?id=1127392 + [The User Timing and Performance Timeline interfaces, which are required for this test, are defined.] + expected: FAIL + diff --git a/testing/web-platform/meta/user-timing/test_user_timing_mark_and_measure_exception_when_invoke_without_parameter.html.ini b/testing/web-platform/meta/user-timing/test_user_timing_mark_and_measure_exception_when_invoke_without_parameter.html.ini new file mode 100644 index 00000000000..3195c9cf007 --- /dev/null +++ b/testing/web-platform/meta/user-timing/test_user_timing_mark_and_measure_exception_when_invoke_without_parameter.html.ini @@ -0,0 +1,8 @@ +[test_user_timing_mark_and_measure_exception_when_invoke_without_parameter.html] + type: testharness + [window.performance.mark() interface is not supported!] + expected: FAIL + + [window.performance.measure() interface is not supported!] + expected: FAIL + diff --git a/testing/web-platform/meta/user-timing/test_user_timing_mark_exceptions.html.ini b/testing/web-platform/meta/user-timing/test_user_timing_mark_exceptions.html.ini new file mode 100644 index 00000000000..f77e05173d5 --- /dev/null +++ b/testing/web-platform/meta/user-timing/test_user_timing_mark_exceptions.html.ini @@ -0,0 +1,5 @@ +[test_user_timing_mark_exceptions.html] + type: testharness + [The User Timing and Performance Timeline interfaces, which are required for this test, are defined.] + expected: FAIL + diff --git a/testing/web-platform/meta/user-timing/test_user_timing_measure.html.ini b/testing/web-platform/meta/user-timing/test_user_timing_measure.html.ini new file mode 100644 index 00000000000..23eecd6e18c --- /dev/null +++ b/testing/web-platform/meta/user-timing/test_user_timing_measure.html.ini @@ -0,0 +1,5 @@ +[test_user_timing_measure.html] + type: testharness + [The User Timing and Performance Timeline interfaces, which are required for this test, are defined.] + expected: FAIL + diff --git a/testing/web-platform/meta/user-timing/test_user_timing_measure_exceptions.html.ini b/testing/web-platform/meta/user-timing/test_user_timing_measure_exceptions.html.ini new file mode 100644 index 00000000000..fbbc11078b8 --- /dev/null +++ b/testing/web-platform/meta/user-timing/test_user_timing_measure_exceptions.html.ini @@ -0,0 +1,5 @@ +[test_user_timing_measure_exceptions.html] + type: testharness + [The User Timing and Performance Timeline interfaces, which are required for this test, are defined.] + expected: FAIL + diff --git a/testing/web-platform/meta/user-timing/test_user_timing_measure_navigation_timing.html.ini b/testing/web-platform/meta/user-timing/test_user_timing_measure_navigation_timing.html.ini new file mode 100644 index 00000000000..f3f4d8b809c --- /dev/null +++ b/testing/web-platform/meta/user-timing/test_user_timing_measure_navigation_timing.html.ini @@ -0,0 +1,5 @@ +[test_user_timing_measure_navigation_timing.html] + type: testharness + [The User Timing and Performance Timeline interfaces, which are required for this test, are defined.] + expected: FAIL + From b18e0cddeab1c520855edc01c751de98d45b25c9 Mon Sep 17 00:00:00 2001 From: Botond Ballo Date: Wed, 14 Jan 2015 18:46:37 -0500 Subject: [PATCH 036/101] Bug 1124452 - Store the widget in ChromeProcessController. r=kats --- gfx/layers/apz/util/ChromeProcessController.cpp | 5 +++++ gfx/layers/apz/util/ChromeProcessController.h | 8 ++++++++ widget/nsBaseWidget.cpp | 2 +- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/gfx/layers/apz/util/ChromeProcessController.cpp b/gfx/layers/apz/util/ChromeProcessController.cpp index b522c913dd4..2c130eeafff 100644 --- a/gfx/layers/apz/util/ChromeProcessController.cpp +++ b/gfx/layers/apz/util/ChromeProcessController.cpp @@ -12,6 +12,11 @@ using namespace mozilla; using namespace mozilla::layers; using namespace mozilla::widget; +ChromeProcessController::ChromeProcessController(nsIWidget* aWidget) + : mWidget(aWidget) +{ +} + void ChromeProcessController::RequestContentRepaint(const FrameMetrics& aFrameMetrics) { diff --git a/gfx/layers/apz/util/ChromeProcessController.h b/gfx/layers/apz/util/ChromeProcessController.h index 9ddbec0d47c..23d2d68ec23 100644 --- a/gfx/layers/apz/util/ChromeProcessController.h +++ b/gfx/layers/apz/util/ChromeProcessController.h @@ -7,6 +7,9 @@ #define mozilla_layers_ChromeProcessController_h #include "mozilla/layers/GeckoContentController.h" +#include "nsCOMPtr.h" + +class nsIWidget; namespace mozilla { @@ -21,6 +24,8 @@ class ChromeProcessController : public mozilla::layers::GeckoContentController typedef mozilla::layers::ScrollableLayerGuid ScrollableLayerGuid; public: + explicit ChromeProcessController(nsIWidget* aWidget); + // GeckoContentController interface virtual void RequestContentRepaint(const FrameMetrics& aFrameMetrics) MOZ_OVERRIDE; virtual void PostDelayedTask(Task* aTask, int aDelayMs) MOZ_OVERRIDE; @@ -38,6 +43,9 @@ public: const ScrollableLayerGuid& aGuid) MOZ_OVERRIDE {} virtual void SendAsyncScrollDOMEvent(bool aIsRoot, const mozilla::CSSRect &aContentRect, const mozilla::CSSSize &aScrollableSize) MOZ_OVERRIDE {} + +private: + nsCOMPtr mWidget; }; } // namespace layers diff --git a/widget/nsBaseWidget.cpp b/widget/nsBaseWidget.cpp index d44d8a6a903..4789757c4db 100644 --- a/widget/nsBaseWidget.cpp +++ b/widget/nsBaseWidget.cpp @@ -935,7 +935,7 @@ void nsBaseWidget::CreateCompositor() already_AddRefed nsBaseWidget::CreateRootContentController() { - nsRefPtr controller = new ChromeProcessController(); + nsRefPtr controller = new ChromeProcessController(this); return controller.forget(); } From 372c6af1efd6d1e5829ea721b64c921787aac7a5 Mon Sep 17 00:00:00 2001 From: Botond Ballo Date: Wed, 14 Jan 2015 18:03:43 -0500 Subject: [PATCH 037/101] Bug 1124452 - Store the main thread's MessageLoop in ChromeProcessController. r=kats --- gfx/layers/apz/util/ChromeProcessController.cpp | 8 +++++++- gfx/layers/apz/util/ChromeProcessController.h | 3 +++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/gfx/layers/apz/util/ChromeProcessController.cpp b/gfx/layers/apz/util/ChromeProcessController.cpp index 2c130eeafff..fb27bf8b25c 100644 --- a/gfx/layers/apz/util/ChromeProcessController.cpp +++ b/gfx/layers/apz/util/ChromeProcessController.cpp @@ -3,7 +3,10 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#include "mozilla/layers/ChromeProcessController.h" +#include "ChromeProcessController.h" + +#include "MainThreadUtils.h" // for NS_IsMainThread() +#include "base/message_loop.h" // for MessageLoop #include "mozilla/layers/CompositorParent.h" #include "mozilla/layers/APZCCallbackHelper.h" #include "nsLayoutUtils.h" @@ -14,7 +17,10 @@ using namespace mozilla::widget; ChromeProcessController::ChromeProcessController(nsIWidget* aWidget) : mWidget(aWidget) + , mUILoop(MessageLoop::current()) { + // Otherwise we're initializing mUILoop incorrectly. + MOZ_ASSERT(NS_IsMainThread()); } void diff --git a/gfx/layers/apz/util/ChromeProcessController.h b/gfx/layers/apz/util/ChromeProcessController.h index 23d2d68ec23..3e8966169e1 100644 --- a/gfx/layers/apz/util/ChromeProcessController.h +++ b/gfx/layers/apz/util/ChromeProcessController.h @@ -11,6 +11,8 @@ class nsIWidget; +class MessageLoop; + namespace mozilla { namespace layers { @@ -46,6 +48,7 @@ public: private: nsCOMPtr mWidget; + MessageLoop* mUILoop; }; } // namespace layers From 7eb43347152d543c0aa3e82073c11decea64ca75 Mon Sep 17 00:00:00 2001 From: Botond Ballo Date: Wed, 28 Jan 2015 13:25:53 -0500 Subject: [PATCH 038/101] Bug 1124452 - Set a displayport for the root scroll frame of the root document in the chrome process. r=kats,tn --- .../apz/util/ChromeProcessController.cpp | 32 +++++++++++++++++++ gfx/layers/apz/util/ChromeProcessController.h | 2 ++ 2 files changed, 34 insertions(+) diff --git a/gfx/layers/apz/util/ChromeProcessController.cpp b/gfx/layers/apz/util/ChromeProcessController.cpp index fb27bf8b25c..bb6c452ec69 100644 --- a/gfx/layers/apz/util/ChromeProcessController.cpp +++ b/gfx/layers/apz/util/ChromeProcessController.cpp @@ -9,7 +9,10 @@ #include "base/message_loop.h" // for MessageLoop #include "mozilla/layers/CompositorParent.h" #include "mozilla/layers/APZCCallbackHelper.h" +#include "nsIDocument.h" +#include "nsIPresShell.h" #include "nsLayoutUtils.h" +#include "nsView.h" using namespace mozilla; using namespace mozilla::layers; @@ -21,6 +24,35 @@ ChromeProcessController::ChromeProcessController(nsIWidget* aWidget) { // Otherwise we're initializing mUILoop incorrectly. MOZ_ASSERT(NS_IsMainThread()); + + mUILoop->PostTask( + FROM_HERE, + NewRunnableMethod(this, &ChromeProcessController::InitializeRoot)); +} + +void +ChromeProcessController::InitializeRoot() +{ + // Create a view-id and set a zero-margin displayport for the root element + // of the root document in the chrome process. This ensures that the scroll + // frame for this element gets an APZC, which in turn ensures that all content + // in the chrome processes is covered by an APZC. + // The displayport is zero-margin because this element is generally not + // actually scrollable (if it is, APZC will set proper margins when it's + // scrolled). + nsView* view = nsView::GetViewFor(mWidget); + MOZ_ASSERT(view); + nsIPresShell* presShell = view->GetPresShell(); + MOZ_ASSERT(presShell); + MOZ_ASSERT(presShell->GetDocument()); + nsIContent* content = presShell->GetDocument()->GetDocumentElement(); + MOZ_ASSERT(content); + uint32_t presShellId; + FrameMetrics::ViewID viewId; + if (APZCCallbackHelper::GetOrCreateScrollIdentifiers(content, &presShellId, &viewId)) { + nsLayoutUtils::SetDisplayPortMargins(content, presShell, ScreenMargin(), 0, + nsLayoutUtils::RepaintMode::DoNotRepaint); + } } void diff --git a/gfx/layers/apz/util/ChromeProcessController.h b/gfx/layers/apz/util/ChromeProcessController.h index 3e8966169e1..d3a708370c5 100644 --- a/gfx/layers/apz/util/ChromeProcessController.h +++ b/gfx/layers/apz/util/ChromeProcessController.h @@ -49,6 +49,8 @@ public: private: nsCOMPtr mWidget; MessageLoop* mUILoop; + + void InitializeRoot(); }; } // namespace layers From 1eec2aacefa5a4d59a7d662177b41afa90babb9b Mon Sep 17 00:00:00 2001 From: Kartikaya Gupta Date: Wed, 28 Jan 2015 13:28:28 -0500 Subject: [PATCH 039/101] Bug 1124452 - Make it work without containerless-root, too. r=tn --- layout/base/nsDisplayList.cpp | 11 ++++------- layout/base/nsDisplayList.h | 1 - layout/generic/nsGfxScrollFrame.cpp | 2 +- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/layout/base/nsDisplayList.cpp b/layout/base/nsDisplayList.cpp index 9d3948e8726..3e66c97b5d5 100644 --- a/layout/base/nsDisplayList.cpp +++ b/layout/base/nsDisplayList.cpp @@ -697,7 +697,6 @@ nsDisplayScrollLayer::ComputeFrameMetrics(nsIFrame* aForFrame, Layer* aLayer, ViewID aScrollParentId, const nsRect& aViewport, - bool aForceNullScrollId, bool aIsRoot, const ContainerLayerParameters& aContainerParameters) { @@ -711,9 +710,7 @@ nsDisplayScrollLayer::ComputeFrameMetrics(nsIFrame* aForFrame, ViewID scrollId = FrameMetrics::NULL_SCROLL_ID; nsIContent* content = aScrollFrame ? aScrollFrame->GetContent() : nullptr; if (content) { - if (!aForceNullScrollId) { - scrollId = nsLayoutUtils::FindOrCreateIDFor(content); - } + scrollId = nsLayoutUtils::FindOrCreateIDFor(content); nsRect dp; if (nsLayoutUtils::GetDisplayPort(content, &dp)) { metrics.SetDisplayPort(CSSRect::FromAppUnits(dp)); @@ -1623,7 +1620,7 @@ already_AddRefed nsDisplayList::PaintRoot(nsDisplayListBuilder* aB nsDisplayScrollLayer::ComputeFrameMetrics(frame, rootScrollFrame, aBuilder->FindReferenceFrameFor(frame), root, FrameMetrics::NULL_SCROLL_ID, viewport, - !isRoot, isRoot, containerParameters)); + isRoot, containerParameters)); } // NS_WARNING is debug-only, so don't even bother checking the conditions in @@ -4088,7 +4085,7 @@ nsDisplaySubDocument::ComputeFrameMetrics(Layer* aLayer, return MakeUnique( nsDisplayScrollLayer::ComputeFrameMetrics(mFrame, rootScrollFrame, ReferenceFrame(), aLayer, mScrollParentId, viewport, - false, isRootContentDocument, params)); + isRootContentDocument, params)); } static bool @@ -4422,7 +4419,7 @@ nsDisplayScrollLayer::ComputeFrameMetrics(Layer* aLayer, return UniquePtr(new FrameMetrics( ComputeFrameMetrics(mScrolledFrame, mScrollFrame, ReferenceFrame(), aLayer, - mScrollParentId, viewport, false, false, params))); + mScrollParentId, viewport, false, params))); } bool diff --git a/layout/base/nsDisplayList.h b/layout/base/nsDisplayList.h index 446847a42f8..c8a0d7cec43 100644 --- a/layout/base/nsDisplayList.h +++ b/layout/base/nsDisplayList.h @@ -3171,7 +3171,6 @@ public: Layer* aLayer, ViewID aScrollParentId, const nsRect& aViewport, - bool aForceNullScrollId, bool aIsRoot, const ContainerLayerParameters& aContainerParameters); diff --git a/layout/generic/nsGfxScrollFrame.cpp b/layout/generic/nsGfxScrollFrame.cpp index 2508895243b..ec73fe0eadf 100644 --- a/layout/generic/nsGfxScrollFrame.cpp +++ b/layout/generic/nsGfxScrollFrame.cpp @@ -3179,7 +3179,7 @@ ScrollFrameHelper::ComputeFrameMetrics(Layer* aLayer, *aOutput->AppendElement() = nsDisplayScrollLayer::ComputeFrameMetrics(mScrolledFrame, mOuter, aContainerReferenceFrame, aLayer, mScrollParentID, - scrollport, false, isRoot, aParameters); + scrollport, isRoot, aParameters); } bool From 30d5b1e414f9ebe0d99f5ee3259c06e5c69f8cb7 Mon Sep 17 00:00:00 2001 From: Kartikaya Gupta Date: Thu, 29 Jan 2015 12:57:55 -0500 Subject: [PATCH 040/101] Bug 1124452 - Ensure the widget continues to be destroyed on the main thread. r=BenWa --- gfx/layers/apz/public/GeckoContentController.h | 1 + gfx/layers/apz/util/ChromeProcessController.cpp | 14 ++++++++++++++ gfx/layers/apz/util/ChromeProcessController.h | 1 + gfx/layers/ipc/CompositorParent.cpp | 7 +++++++ gfx/layers/ipc/CompositorParent.h | 1 + 5 files changed, 24 insertions(+) diff --git a/gfx/layers/apz/public/GeckoContentController.h b/gfx/layers/apz/public/GeckoContentController.h index 4456b588505..c7bf03ea8bd 100644 --- a/gfx/layers/apz/public/GeckoContentController.h +++ b/gfx/layers/apz/public/GeckoContentController.h @@ -157,6 +157,7 @@ public: int aArg = 0) {} GeckoContentController() {} + virtual void Destroy() {} protected: // Protected destructor, to discourage deletion outside of Release(): diff --git a/gfx/layers/apz/util/ChromeProcessController.cpp b/gfx/layers/apz/util/ChromeProcessController.cpp index bb6c452ec69..ef0d9341e7f 100644 --- a/gfx/layers/apz/util/ChromeProcessController.cpp +++ b/gfx/layers/apz/util/ChromeProcessController.cpp @@ -83,3 +83,17 @@ ChromeProcessController::AcknowledgeScrollUpdate(const FrameMetrics::ViewID& aSc { APZCCallbackHelper::AcknowledgeScrollUpdate(aScrollId, aScrollGeneration); } + +void +ChromeProcessController::Destroy() +{ + if (MessageLoop::current() != mUILoop) { + mUILoop->PostTask( + FROM_HERE, + NewRunnableMethod(this, &ChromeProcessController::Destroy)); + return; + } + + MOZ_ASSERT(MessageLoop::current() == mUILoop); + mWidget = nullptr; +} diff --git a/gfx/layers/apz/util/ChromeProcessController.h b/gfx/layers/apz/util/ChromeProcessController.h index d3a708370c5..ec8cda8802c 100644 --- a/gfx/layers/apz/util/ChromeProcessController.h +++ b/gfx/layers/apz/util/ChromeProcessController.h @@ -27,6 +27,7 @@ class ChromeProcessController : public mozilla::layers::GeckoContentController public: explicit ChromeProcessController(nsIWidget* aWidget); + virtual void Destroy() MOZ_OVERRIDE; // GeckoContentController interface virtual void RequestContentRepaint(const FrameMetrics& aFrameMetrics) MOZ_OVERRIDE; diff --git a/gfx/layers/ipc/CompositorParent.cpp b/gfx/layers/ipc/CompositorParent.cpp index 539bee05c8b..edc05af1bb4 100644 --- a/gfx/layers/ipc/CompositorParent.cpp +++ b/gfx/layers/ipc/CompositorParent.cpp @@ -88,6 +88,13 @@ CompositorParent::LayerTreeState::LayerTreeState() { } +CompositorParent::LayerTreeState::~LayerTreeState() +{ + if (mController) { + mController->Destroy(); + } +} + typedef map LayerTreeMap; static LayerTreeMap sIndirectLayerTrees; static StaticAutoPtr sIndirectLayerTreesLock; diff --git a/gfx/layers/ipc/CompositorParent.h b/gfx/layers/ipc/CompositorParent.h index e6e90f0b693..ce80a0300a9 100644 --- a/gfx/layers/ipc/CompositorParent.h +++ b/gfx/layers/ipc/CompositorParent.h @@ -291,6 +291,7 @@ public: struct LayerTreeState { LayerTreeState(); + ~LayerTreeState(); nsRefPtr mRoot; nsRefPtr mController; CompositorParent* mParent; From 289800a0284ded0033860ad5f1c4a029c4f0da84 Mon Sep 17 00:00:00 2001 From: David Keeler Date: Fri, 30 Jan 2015 11:25:24 -0800 Subject: [PATCH 041/101] backout cd0ec3afca5a (bug 832837) for mochitest bustage --- CLOBBER | 2 +- dom/html/HTMLFormElement.cpp | 102 ++----- security/manager/boot/public/moz.build | 1 + .../boot/public/nsISecurityWarningDialogs.idl | 32 +++ security/manager/boot/src/moz.build | 1 + security/manager/boot/src/nsBOOTModule.cpp | 5 + .../boot/src/nsSecureBrowserUIImpl.cpp | 255 ++++++++++++++++++ .../manager/boot/src/nsSecureBrowserUIImpl.h | 26 ++ .../boot/src/nsSecurityWarningDialogs.cpp | 162 +++++++++++ .../boot/src/nsSecurityWarningDialogs.h | 45 ++++ .../en-US/chrome/pipnss/security.properties | 7 + security/manager/locales/jar.mn | 1 + .../en-US/chrome/global/browser.properties | 4 - 13 files changed, 558 insertions(+), 85 deletions(-) create mode 100644 security/manager/boot/public/nsISecurityWarningDialogs.idl create mode 100644 security/manager/boot/src/nsSecurityWarningDialogs.cpp create mode 100644 security/manager/boot/src/nsSecurityWarningDialogs.h create mode 100644 security/manager/locales/en-US/chrome/pipnss/security.properties diff --git a/CLOBBER b/CLOBBER index bb1befee796..e0e95faab14 100644 --- a/CLOBBER +++ b/CLOBBER @@ -22,4 +22,4 @@ # changes to stick? As of bug 928195, this shouldn't be necessary! Please # don't change CLOBBER for WebIDL changes any more. -bug 832837 removes nsISecurityWarningDialogs.idl, which requires a clobber according to bug 1114669 +Bug 1109248 - This needed a CLOBBER on Windows and OSX. diff --git a/dom/html/HTMLFormElement.cpp b/dom/html/HTMLFormElement.cpp index 61d5181ab7a..378a7f7e56e 100644 --- a/dom/html/HTMLFormElement.cpp +++ b/dom/html/HTMLFormElement.cpp @@ -20,6 +20,7 @@ #include "nsPresContext.h" #include "nsIDocument.h" #include "nsIFormControlFrame.h" +#include "nsISecureBrowserUI.h" #include "nsError.h" #include "nsContentUtils.h" #include "nsInterfaceHashtable.h" @@ -32,7 +33,6 @@ #include "mozilla/BinarySearch.h" // form submission -#include "mozilla/Telemetry.h" #include "nsIFormSubmitObserver.h" #include "nsIObserverService.h" #include "nsICategoryManager.h" @@ -45,9 +45,6 @@ #include "nsIDocShell.h" #include "nsFormData.h" #include "nsFormSubmissionConstants.h" -#include "nsIPromptService.h" -#include "nsISecurityUITelemetry.h" -#include "nsIStringBundle.h" // radio buttons #include "mozilla/dom/HTMLInputElement.h" @@ -880,79 +877,24 @@ HTMLFormElement::NotifySubmitObservers(nsIURI* aActionURL, // sXBL/XBL2 issue nsCOMPtr window = OwnerDoc()->GetWindow(); - nsCOMPtr principalURI; - nsresult rv = NodePrincipal()->GetURI(getter_AddRefs(principalURI)); - if (NS_FAILED(rv)) { - return rv; - } - bool formIsHTTPS; - rv = principalURI->SchemeIs("https", &formIsHTTPS); - if (NS_FAILED(rv)) { - return rv; - } - bool actionIsHTTPS; - rv = aActionURL->SchemeIs("https", &actionIsHTTPS); - if (NS_FAILED(rv)) { - return rv; - } - bool actionIsJS; - rv = aActionURL->SchemeIs("javascript", &actionIsJS); - if (NS_FAILED(rv)) { - return rv; - } - if (formIsHTTPS && !actionIsHTTPS && !actionIsJS && !aEarlyNotify) { - nsCOMPtr promptSvc = - do_GetService("@mozilla.org/embedcomp/prompt-service;1"); - if (!promptSvc) { - return NS_ERROR_FAILURE; + // Notify the secure browser UI, if any, that the form is being submitted. + nsCOMPtr docshell = OwnerDoc()->GetDocShell(); + if (docshell && !aEarlyNotify) { + nsCOMPtr secureUI; + docshell->GetSecurityUI(getter_AddRefs(secureUI)); + nsCOMPtr formSubmitObserver = + do_QueryInterface(secureUI); + if (formSubmitObserver) { + nsresult rv = formSubmitObserver->Notify(this, + window, + aActionURL, + aCancelSubmit); + NS_ENSURE_SUCCESS(rv, rv); + + if (*aCancelSubmit) { + return NS_OK; + } } - nsCOMPtr stringBundle; - nsCOMPtr stringBundleService = - mozilla::services::GetStringBundleService(); - if (!stringBundleService) { - return NS_ERROR_FAILURE; - } - rv = stringBundleService->CreateBundle( - "chrome://global/locale/browser.properties", - getter_AddRefs(stringBundle)); - if (NS_FAILED(rv)) { - return rv; - } - nsAutoString title; - nsAutoString message; - nsAutoString cont; - stringBundle->GetStringFromName( - MOZ_UTF16("formPostSecureToInsecureWarning.title"), getter_Copies(title)); - stringBundle->GetStringFromName( - MOZ_UTF16("formPostSecureToInsecureWarning.message"), - getter_Copies(message)); - stringBundle->GetStringFromName( - MOZ_UTF16("formPostSecureToInsecureWarning.continue"), - getter_Copies(cont)); - int32_t buttonPressed; - bool checkState; // this is unused (ConfirmEx requires this parameter) - rv = promptSvc->ConfirmEx(window, title.get(), message.get(), - (nsIPromptService::BUTTON_TITLE_IS_STRING * - nsIPromptService::BUTTON_POS_0) + - (nsIPromptService::BUTTON_TITLE_CANCEL * - nsIPromptService::BUTTON_POS_1), - cont.get(), nullptr, nullptr, nullptr, - &checkState, &buttonPressed); - if (NS_FAILED(rv)) { - return rv; - } - *aCancelSubmit = (buttonPressed == 1); - uint32_t telemetryBucket = - nsISecurityUITelemetry::WARNING_CONFIRM_POST_TO_INSECURE_FROM_SECURE; - mozilla::Telemetry::Accumulate(mozilla::Telemetry::SECURITY_UI, - telemetryBucket); - // Return early if the submission is cancelled. - if (*aCancelSubmit) { - return NS_OK; - } - // The user opted to continue, so note that in the next telemetry bucket. - mozilla::Telemetry::Accumulate(mozilla::Telemetry::SECURITY_UI, - telemetryBucket + 1); } // Notify observers that the form is being submitted. @@ -962,10 +904,10 @@ HTMLFormElement::NotifySubmitObservers(nsIURI* aActionURL, return NS_ERROR_FAILURE; nsCOMPtr theEnum; - rv = service->EnumerateObservers(aEarlyNotify ? - NS_EARLYFORMSUBMIT_SUBJECT : - NS_FORMSUBMIT_SUBJECT, - getter_AddRefs(theEnum)); + nsresult rv = service->EnumerateObservers(aEarlyNotify ? + NS_EARLYFORMSUBMIT_SUBJECT : + NS_FORMSUBMIT_SUBJECT, + getter_AddRefs(theEnum)); NS_ENSURE_SUCCESS(rv, rv); if (theEnum) { diff --git a/security/manager/boot/public/moz.build b/security/manager/boot/public/moz.build index b0879c6d99a..5f841e1c1d1 100644 --- a/security/manager/boot/public/moz.build +++ b/security/manager/boot/public/moz.build @@ -8,6 +8,7 @@ XPIDL_SOURCES += [ 'nsIBufEntropyCollector.idl', 'nsICertBlocklist.idl', 'nsISecurityUITelemetry.idl', + 'nsISecurityWarningDialogs.idl', 'nsISSLStatusProvider.idl', ] diff --git a/security/manager/boot/public/nsISecurityWarningDialogs.idl b/security/manager/boot/public/nsISecurityWarningDialogs.idl new file mode 100644 index 00000000000..d6c812abf43 --- /dev/null +++ b/security/manager/boot/public/nsISecurityWarningDialogs.idl @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIInterfaceRequestor; + +/** + * Functions that display warnings for transitions between secure + * and insecure pages, posts to insecure servers etc. + */ +[scriptable, uuid(a9561631-5964-4d3f-b372-9f23504054b1)] +interface nsISecurityWarningDialogs : nsISupports +{ + /** + * Inform the user: Although the currently displayed + * page was loaded using a secure connection, and the UI probably + * currently indicates a secure page, + * that information is being submitted to an insecure page. + * + * @param ctx A user interface context. + * + * @return true if the user confirms to submit. + */ + boolean confirmPostToInsecureFromSecure(in nsIInterfaceRequestor ctx); +}; + +%{C++ +#define NS_SECURITYWARNINGDIALOGS_CONTRACTID "@mozilla.org/nsSecurityWarningDialogs;1" +%} diff --git a/security/manager/boot/src/moz.build b/security/manager/boot/src/moz.build index e181f84ff67..71078d16f0c 100644 --- a/security/manager/boot/src/moz.build +++ b/security/manager/boot/src/moz.build @@ -15,6 +15,7 @@ UNIFIED_SOURCES += [ 'nsEntropyCollector.cpp', 'nsSecureBrowserUIImpl.cpp', 'nsSecurityHeaderParser.cpp', + 'nsSecurityWarningDialogs.cpp', 'nsSiteSecurityService.cpp', 'PublicKeyPinningService.cpp', 'RootCertificateTelemetryUtils.cpp', diff --git a/security/manager/boot/src/nsBOOTModule.cpp b/security/manager/boot/src/nsBOOTModule.cpp index 52f1a5d2893..62e1b6374a3 100644 --- a/security/manager/boot/src/nsBOOTModule.cpp +++ b/security/manager/boot/src/nsBOOTModule.cpp @@ -8,20 +8,24 @@ #include "CertBlocklist.h" #include "nsEntropyCollector.h" #include "nsSecureBrowserUIImpl.h" +#include "nsSecurityWarningDialogs.h" #include "nsSiteSecurityService.h" NS_GENERIC_FACTORY_CONSTRUCTOR(nsEntropyCollector) NS_GENERIC_FACTORY_CONSTRUCTOR(nsSecureBrowserUIImpl) NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(CertBlocklist, Init) +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsSecurityWarningDialogs, Init) NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsSiteSecurityService, Init) NS_DEFINE_NAMED_CID(NS_ENTROPYCOLLECTOR_CID); +NS_DEFINE_NAMED_CID(NS_SECURITYWARNINGDIALOGS_CID); NS_DEFINE_NAMED_CID(NS_SECURE_BROWSER_UI_CID); NS_DEFINE_NAMED_CID(NS_SITE_SECURITY_SERVICE_CID); NS_DEFINE_NAMED_CID(NS_CERT_BLOCKLIST_CID); static const mozilla::Module::CIDEntry kBOOTCIDs[] = { { &kNS_ENTROPYCOLLECTOR_CID, false, nullptr, nsEntropyCollectorConstructor }, + { &kNS_SECURITYWARNINGDIALOGS_CID, false, nullptr, nsSecurityWarningDialogsConstructor }, { &kNS_SECURE_BROWSER_UI_CID, false, nullptr, nsSecureBrowserUIImplConstructor }, { &kNS_SITE_SECURITY_SERVICE_CID, false, nullptr, nsSiteSecurityServiceConstructor }, { &kNS_CERT_BLOCKLIST_CID, false, nullptr, CertBlocklistConstructor}, @@ -30,6 +34,7 @@ static const mozilla::Module::CIDEntry kBOOTCIDs[] = { static const mozilla::Module::ContractIDEntry kBOOTContracts[] = { { NS_ENTROPYCOLLECTOR_CONTRACTID, &kNS_ENTROPYCOLLECTOR_CID }, + { NS_SECURITYWARNINGDIALOGS_CONTRACTID, &kNS_SECURITYWARNINGDIALOGS_CID }, { NS_SECURE_BROWSER_UI_CONTRACTID, &kNS_SECURE_BROWSER_UI_CID }, { NS_SSSERVICE_CONTRACTID, &kNS_SITE_SECURITY_SERVICE_CID }, { NS_CERTBLOCKLIST_CONTRACTID, &kNS_CERT_BLOCKLIST_CID }, diff --git a/security/manager/boot/src/nsSecureBrowserUIImpl.cpp b/security/manager/boot/src/nsSecureBrowserUIImpl.cpp index a5b6bd9907d..132855f3ebb 100644 --- a/security/manager/boot/src/nsSecureBrowserUIImpl.cpp +++ b/security/manager/boot/src/nsSecureBrowserUIImpl.cpp @@ -9,13 +9,17 @@ #include "nsISecureBrowserUI.h" #include "nsSecureBrowserUIImpl.h" #include "nsCOMPtr.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" #include "nsIServiceManager.h" #include "nsCURILoader.h" #include "nsIDocShell.h" #include "nsIDocShellTreeItem.h" #include "nsIDocument.h" +#include "nsIPrincipal.h" #include "nsIDOMElement.h" #include "nsPIDOMWindow.h" +#include "nsIContent.h" #include "nsIWebProgress.h" #include "nsIWebProgressListener.h" #include "nsIChannel.h" @@ -27,6 +31,9 @@ #include "nsISSLStatus.h" #include "nsIURI.h" #include "nsISecurityEventSink.h" +#include "nsIPrompt.h" +#include "nsIFormSubmitObserver.h" +#include "nsISecurityWarningDialogs.h" #include "nsISecurityInfoProvider.h" #include "imgIRequest.h" #include "nsThreadUtils.h" @@ -134,6 +141,7 @@ nsSecureBrowserUIImpl::~nsSecureBrowserUIImpl() NS_IMPL_ISUPPORTS(nsSecureBrowserUIImpl, nsISecureBrowserUI, nsIWebProgressListener, + nsIFormSubmitObserver, nsISupportsWeakReference, nsISSLStatusProvider) @@ -303,6 +311,25 @@ nsSecureBrowserUIImpl::SetDocShell(nsIDocShell *aDocShell) return rv; } +static nsresult IsChildOfDomWindow(nsIDOMWindow *parent, nsIDOMWindow *child, + bool* value) +{ + *value = false; + + if (parent == child) { + *value = true; + return NS_OK; + } + + nsCOMPtr childsParent; + child->GetParent(getter_AddRefs(childsParent)); + + if (childsParent && childsParent.get() != child) + IsChildOfDomWindow(parent, childsParent, value); + + return NS_OK; +} + static uint32_t GetSecurityStateFromSecurityInfoAndRequest(nsISupports* info, nsIRequest* request) { @@ -356,6 +383,72 @@ static uint32_t GetSecurityStateFromSecurityInfoAndRequest(nsISupports* info, } +NS_IMETHODIMP +nsSecureBrowserUIImpl::Notify(nsIDOMHTMLFormElement* aDOMForm, + nsIDOMWindow* aWindow, nsIURI* actionURL, + bool* cancelSubmit) +{ + // Return NS_OK unless we want to prevent this form from submitting. + *cancelSubmit = false; + if (!aWindow || !actionURL || !aDOMForm) + return NS_OK; + + nsCOMPtr formNode = do_QueryInterface(aDOMForm); + + nsCOMPtr document = formNode->GetComposedDoc(); + if (!document) return NS_OK; + + nsIPrincipal *principal = formNode->NodePrincipal(); + + if (!principal) + { + *cancelSubmit = true; + return NS_OK; + } + + nsCOMPtr formURL; + if (NS_FAILED(principal->GetURI(getter_AddRefs(formURL))) || + !formURL) + { + formURL = document->GetDocumentURI(); + } + + nsCOMPtr postingWindow = + do_QueryInterface(document->GetWindow()); + // We can't find this document's window, cancel it. + if (!postingWindow) + { + NS_WARNING("If you see this and can explain why it should be allowed, note in Bug 332324"); + *cancelSubmit = true; + return NS_OK; + } + + nsCOMPtr window; + { + ReentrantMonitorAutoEnter lock(mReentrantMonitor); + window = do_QueryReferent(mWindow); + + // The window was destroyed, so we assume no form was submitted within it. + if (!window) + return NS_OK; + } + + bool isChild; + IsChildOfDomWindow(window, postingWindow, &isChild); + + // This notify call is not for our window, ignore it. + if (!isChild) + return NS_OK; + + bool okayToPost; + nsresult res = CheckPost(formURL, actionURL, &okayToPost); + + if (NS_SUCCEEDED(res) && !okayToPost) + *cancelSubmit = true; + + return res; +} + // nsIWebProgressListener NS_IMETHODIMP nsSecureBrowserUIImpl::OnProgressChange(nsIWebProgress* aWebProgress, @@ -1415,3 +1508,165 @@ nsSecureBrowserUIImpl::GetSSLStatus(nsISSLStatus** _result) return NS_OK; } + +nsresult +nsSecureBrowserUIImpl::IsURLHTTPS(nsIURI* aURL, bool* value) +{ + *value = false; + + if (!aURL) + return NS_OK; + + return aURL->SchemeIs("https", value); +} + +nsresult +nsSecureBrowserUIImpl::IsURLJavaScript(nsIURI* aURL, bool* value) +{ + *value = false; + + if (!aURL) + return NS_OK; + + return aURL->SchemeIs("javascript", value); +} + +nsresult +nsSecureBrowserUIImpl::CheckPost(nsIURI *formURL, nsIURI *actionURL, bool *okayToPost) +{ + bool formSecure, actionSecure, actionJavaScript; + *okayToPost = true; + + nsresult rv = IsURLHTTPS(formURL, &formSecure); + if (NS_FAILED(rv)) + return rv; + + rv = IsURLHTTPS(actionURL, &actionSecure); + if (NS_FAILED(rv)) + return rv; + + rv = IsURLJavaScript(actionURL, &actionJavaScript); + if (NS_FAILED(rv)) + return rv; + + // If we are posting to a secure link, all is okay. + // It doesn't matter whether the currently viewed page is secure or not, + // because the data will be sent to a secure URL. + if (actionSecure) { + return NS_OK; + } + + // Action is a JavaScript call, not an actual post. That's okay too. + if (actionJavaScript) { + return NS_OK; + } + + // posting to insecure webpage from a secure webpage. + if (formSecure) { + *okayToPost = ConfirmPostToInsecureFromSecure(); + } + + return NS_OK; +} + +// +// Implementation of an nsIInterfaceRequestor for use +// as context for NSS calls +// +class nsUIContext : public nsIInterfaceRequestor +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIINTERFACEREQUESTOR + + explicit nsUIContext(nsIDOMWindow *window); + +protected: + virtual ~nsUIContext(); + +private: + nsCOMPtr mWindow; +}; + +NS_IMPL_ISUPPORTS(nsUIContext, nsIInterfaceRequestor) + +nsUIContext::nsUIContext(nsIDOMWindow *aWindow) +: mWindow(aWindow) +{ +} + +nsUIContext::~nsUIContext() +{ +} + +/* void getInterface (in nsIIDRef uuid, [iid_is (uuid), retval] out nsQIResult result); */ +NS_IMETHODIMP nsUIContext::GetInterface(const nsIID & uuid, void * *result) +{ + NS_ENSURE_TRUE(mWindow, NS_ERROR_FAILURE); + nsresult rv; + + if (uuid.Equals(NS_GET_IID(nsIPrompt))) { + nsCOMPtr window = do_QueryInterface(mWindow, &rv); + if (NS_FAILED(rv)) return rv; + + nsIPrompt *prompt; + + rv = window->GetPrompter(&prompt); + *result = prompt; + } else if (uuid.Equals(NS_GET_IID(nsIDOMWindow))) { + *result = mWindow; + NS_ADDREF ((nsISupports*) *result); + rv = NS_OK; + } else { + rv = NS_ERROR_NO_INTERFACE; + } + + return rv; +} + +bool +nsSecureBrowserUIImpl::GetNSSDialogs(nsCOMPtr & dialogs, + nsCOMPtr & ctx) +{ + if (!NS_IsMainThread()) { + NS_ERROR("nsSecureBrowserUIImpl::GetNSSDialogs called off the main thread"); + return false; + } + + dialogs = do_GetService(NS_SECURITYWARNINGDIALOGS_CONTRACTID); + if (!dialogs) + return false; + + nsCOMPtr window; + { + ReentrantMonitorAutoEnter lock(mReentrantMonitor); + window = do_QueryReferent(mWindow); + NS_ASSERTION(window, "Window has gone away?!"); + } + ctx = new nsUIContext(window); + + return true; +} + +/** + * ConfirmPostToInsecureFromSecure - returns true if + * the user approves the submit (or doesn't care). + * returns false on errors. + */ +bool nsSecureBrowserUIImpl:: +ConfirmPostToInsecureFromSecure() +{ + nsCOMPtr dialogs; + nsCOMPtr ctx; + + if (!GetNSSDialogs(dialogs, ctx)) { + return false; // Should this allow true for unimplemented? + } + + bool result; + + nsresult rv = dialogs->ConfirmPostToInsecureFromSecure(ctx, &result); + if (NS_FAILED(rv)) return false; + + return result; +} diff --git a/security/manager/boot/src/nsSecureBrowserUIImpl.h b/security/manager/boot/src/nsSecureBrowserUIImpl.h index 4dd0792b6ed..5f7b4edda05 100644 --- a/security/manager/boot/src/nsSecureBrowserUIImpl.h +++ b/security/manager/boot/src/nsSecureBrowserUIImpl.h @@ -14,10 +14,12 @@ #include "nsString.h" #include "nsIDOMElement.h" #include "nsIDOMWindow.h" +#include "nsIDOMHTMLFormElement.h" #include "nsISecureBrowserUI.h" #include "nsIDocShell.h" #include "nsIDocShellTreeItem.h" #include "nsIWebProgressListener.h" +#include "nsIFormSubmitObserver.h" #include "nsIURI.h" #include "nsISecurityEventSink.h" #include "nsWeakReference.h" @@ -27,7 +29,10 @@ #include "nsINetUtil.h" class nsISSLStatus; +class nsITransportSecurityInfo; +class nsISecurityWarningDialogs; class nsIChannel; +class nsIInterfaceRequestor; #define NS_SECURE_BROWSER_UI_CID \ { 0xcc75499a, 0x1dd1, 0x11b2, {0x8a, 0x82, 0xca, 0x41, 0x0a, 0xc9, 0x07, 0xb8}} @@ -35,6 +40,7 @@ class nsIChannel; class nsSecureBrowserUIImpl : public nsISecureBrowserUI, public nsIWebProgressListener, + public nsIFormSubmitObserver, public nsSupportsWeakReference, public nsISSLStatusProvider { @@ -45,8 +51,14 @@ public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIWEBPROGRESSLISTENER NS_DECL_NSISECUREBROWSERUI + NS_DECL_NSISSLSTATUSPROVIDER + NS_IMETHOD Notify(nsIDOMHTMLFormElement* formNode, nsIDOMWindow* window, + nsIURI *actionURL, bool* cancelSubmit) MOZ_OVERRIDE; + NS_IMETHOD NotifyInvalidSubmit(nsIDOMHTMLFormElement* formNode, + nsIArray* invalidElements) MOZ_OVERRIDE { return NS_OK; } + protected: virtual ~nsSecureBrowserUIImpl(); @@ -100,6 +112,20 @@ protected: nsCOMPtr mSSLStatus; nsCOMPtr mCurrentToplevelSecurityInfo; + nsresult CheckPost(nsIURI *formURI, nsIURI *actionURL, bool *okayToPost); + nsresult IsURLHTTPS(nsIURI* aURL, bool *value); + nsresult IsURLJavaScript(nsIURI* aURL, bool *value); + + bool ConfirmEnteringSecure(); + bool ConfirmEnteringWeak(); + bool ConfirmLeavingSecure(); + bool ConfirmMixedMode(); + bool ConfirmPostToInsecure(); + bool ConfirmPostToInsecureFromSecure(); + + bool GetNSSDialogs(nsCOMPtr & dialogs, + nsCOMPtr & window); + PLDHashTable mTransferringRequests; }; diff --git a/security/manager/boot/src/nsSecurityWarningDialogs.cpp b/security/manager/boot/src/nsSecurityWarningDialogs.cpp new file mode 100644 index 00000000000..110afc7b92d --- /dev/null +++ b/security/manager/boot/src/nsSecurityWarningDialogs.cpp @@ -0,0 +1,162 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsSecurityWarningDialogs.h" +#include "nsIComponentManager.h" +#include "nsIServiceManager.h" +#include "nsReadableUtils.h" +#include "nsString.h" +#include "nsIPrompt.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsThreadUtils.h" + +#include "mozilla/Telemetry.h" +#include "nsISecurityUITelemetry.h" + +NS_IMPL_ISUPPORTS(nsSecurityWarningDialogs, nsISecurityWarningDialogs) + +#define STRING_BUNDLE_URL "chrome://pipnss/locale/security.properties" + +nsSecurityWarningDialogs::nsSecurityWarningDialogs() +{ +} + +nsSecurityWarningDialogs::~nsSecurityWarningDialogs() +{ +} + +nsresult +nsSecurityWarningDialogs::Init() +{ + nsresult rv; + + mPrefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr service = + do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + + rv = service->CreateBundle(STRING_BUNDLE_URL, + getter_AddRefs(mStringBundle)); + return rv; +} + +NS_IMETHODIMP +nsSecurityWarningDialogs::ConfirmPostToInsecureFromSecure(nsIInterfaceRequestor *ctx, bool* _result) +{ + nsresult rv; + + // The Telemetry clickthrough constant is 1 more than the constant for the dialog. + rv = ConfirmDialog(ctx, nullptr, // No preference for this one - it's too important + MOZ_UTF16("PostToInsecureFromSecureMessage"), + nullptr, + nsISecurityUITelemetry::WARNING_CONFIRM_POST_TO_INSECURE_FROM_SECURE, + _result); + + return rv; +} + +nsresult +nsSecurityWarningDialogs::ConfirmDialog(nsIInterfaceRequestor *ctx, const char *prefName, + const char16_t *messageName, + const char16_t *showAgainName, + const uint32_t aBucket, + bool* _result) +{ + nsresult rv; + + // Get user's preference for this alert + // prefName, showAgainName are null if there is no preference for this dialog + bool prefValue = true; + + if (prefName) { + rv = mPrefBranch->GetBoolPref(prefName, &prefValue); + if (NS_FAILED(rv)) prefValue = true; + } + + // Stop if confirm is not requested + if (!prefValue) { + *_result = true; + return NS_OK; + } + + MOZ_ASSERT(NS_IsMainThread()); + mozilla::Telemetry::Accumulate(mozilla::Telemetry::SECURITY_UI, aBucket); + // See AlertDialog() for a description of how showOnce works. + nsAutoCString showOncePref(prefName); + showOncePref += ".show_once"; + + bool showOnce = false; + mPrefBranch->GetBoolPref(showOncePref.get(), &showOnce); + + if (showOnce) + prefValue = false; + + // Get Prompt to use + nsCOMPtr prompt = do_GetInterface(ctx); + if (!prompt) return NS_ERROR_FAILURE; + + // Get messages strings from localization file + nsXPIDLString windowTitle, message, alertMe, cont; + + mStringBundle->GetStringFromName(MOZ_UTF16("Title"), + getter_Copies(windowTitle)); + mStringBundle->GetStringFromName(messageName, + getter_Copies(message)); + if (showAgainName) { + mStringBundle->GetStringFromName(showAgainName, + getter_Copies(alertMe)); + } + mStringBundle->GetStringFromName(MOZ_UTF16("Continue"), + getter_Copies(cont)); + // alertMe is allowed to be null + if (!windowTitle || !message || !cont) return NS_ERROR_FAILURE; + + // Replace # characters with newlines to lay out the dialog. + char16_t* msgchars = message.BeginWriting(); + + uint32_t i = 0; + for (i = 0; msgchars[i] != '\0'; i++) { + if (msgchars[i] == '#') { + msgchars[i] = '\n'; + } + } + + int32_t buttonPressed; + + rv = prompt->ConfirmEx(windowTitle, + message, + (nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_0) + + (nsIPrompt::BUTTON_TITLE_CANCEL * nsIPrompt::BUTTON_POS_1), + cont, + nullptr, + nullptr, + alertMe, + &prefValue, + &buttonPressed); + + if (NS_FAILED(rv)) return rv; + + *_result = (buttonPressed != 1); + if (*_result) { + // For confirmation dialogs, the clickthrough constant is 1 more + // than the constant for the dialog. + mozilla::Telemetry::Accumulate(mozilla::Telemetry::SECURITY_UI, aBucket + 1); + } + + if (!prefValue && prefName) { + mPrefBranch->SetBoolPref(prefName, false); + } else if (prefValue && showOnce) { + mPrefBranch->SetBoolPref(showOncePref.get(), false); + } + + return rv; +} + diff --git a/security/manager/boot/src/nsSecurityWarningDialogs.h b/security/manager/boot/src/nsSecurityWarningDialogs.h new file mode 100644 index 00000000000..0a9313bae92 --- /dev/null +++ b/security/manager/boot/src/nsSecurityWarningDialogs.h @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsSecurityWarningDialogs_h +#define nsSecurityWarningDialogs_h + +#include "nsISecurityWarningDialogs.h" +#include "nsIPrefBranch.h" +#include "nsIStringBundle.h" +#include "nsCOMPtr.h" + +class nsSecurityWarningDialogs : public nsISecurityWarningDialogs +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISECURITYWARNINGDIALOGS + + nsSecurityWarningDialogs(); + + nsresult Init(); + +protected: + virtual ~nsSecurityWarningDialogs(); + + nsresult AlertDialog(nsIInterfaceRequestor *ctx, const char *prefName, + const char16_t *messageName, + const char16_t *showAgainName, + bool aAsync, const uint32_t aBucket); + nsresult ConfirmDialog(nsIInterfaceRequestor *ctx, const char *prefName, + const char16_t *messageName, + const char16_t *showAgainName, const uint32_t aBucket, + bool* _result); + nsCOMPtr mStringBundle; + nsCOMPtr mPrefBranch; +}; + +#define NS_SECURITYWARNINGDIALOGS_CID \ + { /* 8d995d4f-adcc-4159-b7f1-e94af72eeb88 */ \ + 0x8d995d4f, 0xadcc, 0x4159, \ + {0xb7, 0xf1, 0xe9, 0x4a, 0xf7, 0x2e, 0xeb, 0x88} } + +#endif diff --git a/security/manager/locales/en-US/chrome/pipnss/security.properties b/security/manager/locales/en-US/chrome/pipnss/security.properties new file mode 100644 index 00000000000..50af92694e0 --- /dev/null +++ b/security/manager/locales/en-US/chrome/pipnss/security.properties @@ -0,0 +1,7 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Title=Security Warning +PostToInsecureFromSecureMessage=Although this page is encrypted, the information you have entered is to be sent over an unencrypted connection and could easily be read by a third party.##Are you sure you want to continue sending this information?## +Continue=Continue diff --git a/security/manager/locales/jar.mn b/security/manager/locales/jar.mn index 9b5c2da4461..66c1535f3b6 100644 --- a/security/manager/locales/jar.mn +++ b/security/manager/locales/jar.mn @@ -9,6 +9,7 @@ % locale pippki @AB_CD@ %locale/@AB_CD@/pippki/ locale/@AB_CD@/pipnss/pipnss.properties (%chrome/pipnss/pipnss.properties) locale/@AB_CD@/pipnss/nsserrors.properties (%chrome/pipnss/nsserrors.properties) + locale/@AB_CD@/pipnss/security.properties (%chrome/pipnss/security.properties) locale/@AB_CD@/pippki/pippki.dtd (%chrome/pippki/pippki.dtd) locale/@AB_CD@/pippki/pippki.properties (%chrome/pippki/pippki.properties) locale/@AB_CD@/pippki/certManager.dtd (%chrome/pippki/certManager.dtd) diff --git a/toolkit/locales/en-US/chrome/global/browser.properties b/toolkit/locales/en-US/chrome/global/browser.properties index 2b315062918..0e5409fd1cc 100644 --- a/toolkit/locales/en-US/chrome/global/browser.properties +++ b/toolkit/locales/en-US/chrome/global/browser.properties @@ -8,7 +8,3 @@ browsewithcaret.checkLabel=Pressing F7 turns Caret Browsing on or off. This feat browsewithcaret.checkButtonLabel=Yes plainText.wordWrap=Wrap Long Lines - -formPostSecureToInsecureWarning.title = Security Warning -formPostSecureToInsecureWarning.message = The information you have entered on this page will be sent over an insecure connection and could be read by a third party.\n\nAre you sure you want to send this information? -formPostSecureToInsecureWarning.continue = Continue From 8cc9b7897674306387fc393eecd1aecb7eca9701 Mon Sep 17 00:00:00 2001 From: Ms2ger Date: Fri, 30 Jan 2015 20:38:45 +0100 Subject: [PATCH 042/101] Bug 1127943 - Make testharness.js the default template for gen_template.pl; r=bz (NPOTB, DONTBUILD) --- testing/mochitest/gen_template.pl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/testing/mochitest/gen_template.pl b/testing/mochitest/gen_template.pl index 5dbd81dc22b..5212d5e8217 100644 --- a/testing/mochitest/gen_template.pl +++ b/testing/mochitest/gen_template.pl @@ -6,8 +6,8 @@ # It takes two arguments: # # -b: a bugnumber -# -type: template type. One of {html|xhtml|xul|th|chrome|chromexul}. -# Defaults to html. +# -type: template type. One of {plain|xhtml|xul|th|chrome|chromexul}. +# Defaults to th (testharness.js). # # For example, this command: # @@ -28,10 +28,10 @@ if ($template_type eq "xul") { $template_type = "$FindBin::RealBin/static/chrome.template.txt"; } elsif ($template_type eq "chromexul") { $template_type = "$FindBin::RealBin/static/chromexul.template.txt"; -} elsif ($template_type eq "th") { - $template_type = "$FindBin::RealBin/static/th.template.txt"; -} else { +} elsif ($template_type eq "plain") { $template_type = "$FindBin::RealBin/static/test.template.txt"; +} else { + $template_type = "$FindBin::RealBin/static/th.template.txt"; } open(IN,$template_type) or die("Failed to open myfile for reading."); From fc0410340bbe26a49fcd46627b5988a92843c9ca Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Fri, 30 Jan 2015 14:49:32 -0500 Subject: [PATCH 043/101] Bug 1127498 - Share one buffer between all threads, improve marker lifetime management, some code cleanup. r=BenWa --- tools/profiler/BreakpadSampler.cpp | 32 +- tools/profiler/ProfileEntry.cpp | 425 ++++++++++-------- tools/profiler/ProfileEntry.h | 83 +++- tools/profiler/PseudoStack.h | 113 ++--- tools/profiler/SyncProfile.cpp | 3 +- tools/profiler/TableTicker.cpp | 67 +-- tools/profiler/TableTicker.h | 10 +- tools/profiler/UnwinderThread2.cpp | 16 +- tools/profiler/platform-linux.cc | 3 +- tools/profiler/platform-macos.cc | 3 +- tools/profiler/platform-win32.cc | 4 +- tools/profiler/platform.cpp | 48 -- tools/profiler/platform.h | 2 + .../tests/gtest/ThreadProfileTest.cpp | 45 +- 14 files changed, 396 insertions(+), 458 deletions(-) diff --git a/tools/profiler/BreakpadSampler.cpp b/tools/profiler/BreakpadSampler.cpp index 0f3a64e3858..96ba1d66dd8 100644 --- a/tools/profiler/BreakpadSampler.cpp +++ b/tools/profiler/BreakpadSampler.cpp @@ -155,17 +155,16 @@ void genPseudoBacktraceEntries(/*MODIFIED*/UnwinderThreadBuffer* utb, // RUNS IN SIGHANDLER CONTEXT static void populateBuffer(UnwinderThreadBuffer* utb, TickSample* sample, - UTB_RELEASE_FUNC releaseFunction, bool jankOnly) + UTB_RELEASE_FUNC releaseFunction) { ThreadProfile& sampledThreadProfile = *sample->threadProfile; PseudoStack* stack = sampledThreadProfile.GetPseudoStack(); - stack->updateGeneration(sampledThreadProfile.GetGenerationID()); /* Manufacture the ProfileEntries that we will give to the unwinder thread, and park them in |utb|. */ bool recordSample = true; - /* Don't process the PeudoStack's markers or honour jankOnly if we're + /* Don't process the PeudoStack's markers if we're immediately sampling the current thread. */ if (!sample->isSamplingCurrentThread) { // LinkedUWTBuffers before markers @@ -178,30 +177,9 @@ void populateBuffer(UnwinderThreadBuffer* utb, TickSample* sample, ProfilerMarkerLinkedList* pendingMarkersList = stack->getPendingMarkers(); while (pendingMarkersList && pendingMarkersList->peek()) { ProfilerMarker* marker = pendingMarkersList->popHead(); - stack->addStoredMarker(marker); + sampledThreadProfile.addStoredMarker(marker); utb__addEntry( utb, ProfileEntry('m', marker) ); } - if (jankOnly) { - // if we are on a different event we can discard any temporary samples - // we've kept around - if (sLastSampledEventGeneration != sCurrentEventGeneration) { - // XXX: we also probably want to add an entry to the profile to help - // distinguish which samples are part of the same event. That, or record - // the event generation in each sample - sampledThreadProfile.erase(); - } - sLastSampledEventGeneration = sCurrentEventGeneration; - - recordSample = false; - // only record the events when we have a we haven't seen a tracer - // event for 100ms - if (!sLastTracerEvent.IsNull()) { - mozilla::TimeDuration delta = sample->timestamp - sLastTracerEvent; - if (delta.ToMilliseconds() > 100.0) { - recordSample = true; - } - } - } } // JRS 2012-Sept-27: this logic used to involve mUseStackWalk. @@ -318,7 +296,7 @@ void sampleCurrent(TickSample* sample) return; } UnwinderThreadBuffer* utb = syncBuf->GetBuffer(); - populateBuffer(utb, sample, &utb__finish_sync_buffer, false); + populateBuffer(utb, sample, &utb__finish_sync_buffer); } // RUNS IN SIGHANDLER CONTEXT @@ -344,7 +322,7 @@ void TableTicker::UnwinderTick(TickSample* sample) if (!utb) return; - populateBuffer(utb, sample, &uwt__release_full_buffer, mJankOnly); + populateBuffer(utb, sample, &uwt__release_full_buffer); } // END take samples diff --git a/tools/profiler/ProfileEntry.cpp b/tools/profiler/ProfileEntry.cpp index 247f39e9ff3..8f17736a3d9 100644 --- a/tools/profiler/ProfileEntry.cpp +++ b/tools/profiler/ProfileEntry.cpp @@ -94,7 +94,7 @@ void ProfileEntry::log() // mTagMarker (ProfilerMarker*) m // mTagData (const char*) c,s // mTagPtr (void*) d,l,L,B (immediate backtrace), S(start-of-stack) - // mTagInt (int) n,f,y + // mTagInt (int) n,f,y,T (thread id) // mTagChar (char) h // mTagFloat (double) r,t,p,R (resident memory), U (unshared memory) switch (mTagName) { @@ -104,7 +104,7 @@ void ProfileEntry::log() LOGF("%c \"%s\"", mTagName, mTagData); break; case 'd': case 'l': case 'L': case 'B': case 'S': LOGF("%c %p", mTagName, mTagPtr); break; - case 'n': case 'f': case 'y': + case 'n': case 'f': case 'y': case 'T': LOGF("%c %d", mTagName, mTagInt); break; case 'h': LOGF("%c \'%c\'", mTagName, mTagChar); break; @@ -139,126 +139,50 @@ std::ostream& operator<<(std::ostream& stream, const ProfileEntry& entry) //////////////////////////////////////////////////////////////////////// -// BEGIN ThreadProfile +// BEGIN ProfileBuffer -#define DYNAMIC_MAX_STRING 512 - -ThreadProfile::ThreadProfile(ThreadInfo* aInfo, int aEntrySize) - : mThreadInfo(aInfo) +ProfileBuffer::ProfileBuffer(int aEntrySize) + : mEntries(MakeUnique(aEntrySize)) , mWritePos(0) - , mLastFlushPos(0) , mReadPos(0) , mEntrySize(aEntrySize) - , mPseudoStack(aInfo->Stack()) - , mMutex("ThreadProfile::mMutex") - , mThreadId(aInfo->ThreadId()) - , mIsMainThread(aInfo->IsMainThread()) - , mPlatformData(aInfo->GetPlatformData()) , mGeneration(0) - , mPendingGenerationFlush(0) - , mStackTop(aInfo->StackTop()) - , mRespInfo(this) -#ifdef XP_LINUX - , mRssMemory(0) - , mUssMemory(0) -#endif { - MOZ_COUNT_CTOR(ThreadProfile); - mEntries = new ProfileEntry[mEntrySize]; } -ThreadProfile::~ThreadProfile() +// Called from signal, call only reentrant functions +void ProfileBuffer::addTag(const ProfileEntry& aTag) { - MOZ_COUNT_DTOR(ThreadProfile); - delete[] mEntries; -} - -void ThreadProfile::addTag(ProfileEntry aTag) -{ - // Called from signal, call only reentrant functions - mEntries[mWritePos] = aTag; - mWritePos = mWritePos + 1; - if (mWritePos >= mEntrySize) { - mPendingGenerationFlush++; - mWritePos = mWritePos % mEntrySize; + mEntries[mWritePos++] = aTag; + if (mWritePos == mEntrySize) { + mGeneration++; + mWritePos = 0; } if (mWritePos == mReadPos) { - // Keep one slot open + // Keep one slot open. mEntries[mReadPos] = ProfileEntry(); mReadPos = (mReadPos + 1) % mEntrySize; } - // we also need to move the flush pos to ensure we - // do not pass it - if (mWritePos == mLastFlushPos) { - mLastFlushPos = (mLastFlushPos + 1) % mEntrySize; +} + +void ProfileBuffer::addStoredMarker(ProfilerMarker *aStoredMarker) { + aStoredMarker->SetGeneration(mGeneration); + mStoredMarkers.insert(aStoredMarker); +} + +void ProfileBuffer::deleteExpiredStoredMarkers() { + // Delete markers of samples that have been overwritten due to circular + // buffer wraparound. + int generation = mGeneration; + while (mStoredMarkers.peek() && + mStoredMarkers.peek()->HasExpired(generation)) { + delete mStoredMarkers.popHead(); } } -// flush the new entries -void ThreadProfile::flush() -{ - mLastFlushPos = mWritePos; - mGeneration += mPendingGenerationFlush; - mPendingGenerationFlush = 0; -} +#define DYNAMIC_MAX_STRING 512 -// discards all of the entries since the last flush() -// NOTE: that if mWritePos happens to wrap around past -// mLastFlushPos we actually only discard mWritePos - mLastFlushPos entries -// -// r = mReadPos -// w = mWritePos -// f = mLastFlushPos -// -// r f w -// |-----------------------------| -// | abcdefghijklmnopq | -> 'abcdefghijklmnopq' -// |-----------------------------| -// -// -// mWritePos and mReadPos have passed mLastFlushPos -// f -// w r -// |-----------------------------| -// |ABCDEFGHIJKLMNOPQRSqrstuvwxyz| -// |-----------------------------| -// w -// r -// |-----------------------------| -// |ABCDEFGHIJKLMNOPQRSqrstuvwxyz| -> '' -// |-----------------------------| -// -// -// mWritePos will end up the same as mReadPos -// r -// w f -// |-----------------------------| -// |ABCDEFGHIJKLMklmnopqrstuvwxyz| -// |-----------------------------| -// r -// w -// |-----------------------------| -// |ABCDEFGHIJKLMklmnopqrstuvwxyz| -> '' -// |-----------------------------| -// -// -// mWritePos has moved past mReadPos -// w r f -// |-----------------------------| -// |ABCDEFdefghijklmnopqrstuvwxyz| -// |-----------------------------| -// r w -// |-----------------------------| -// |ABCDEFdefghijklmnopqrstuvwxyz| -> 'defghijkl' -// |-----------------------------| - -void ThreadProfile::erase() -{ - mWritePos = mLastFlushPos; - mPendingGenerationFlush = 0; -} - -char* ThreadProfile::processDynamicTag(int readPos, +char* ProfileBuffer::processDynamicTag(int readPos, int* tagsConsumed, char* tagBuff) { int readAheadPos = (readPos + 1) % mEntrySize; @@ -266,7 +190,7 @@ char* ThreadProfile::processDynamicTag(int readPos, // Read the string stored in mTagData until the null character is seen bool seenNullByte = false; - while (readAheadPos != mLastFlushPos && !seenNullByte) { + while (readAheadPos != mWritePos && !seenNullByte) { (*tagsConsumed)++; ProfileEntry readAheadEntry = mEntries[readAheadPos]; for (size_t pos = 0; pos < sizeof(void*); pos++) { @@ -283,16 +207,25 @@ char* ThreadProfile::processDynamicTag(int readPos, return tagBuff; } -void ThreadProfile::IterateTags(IterateTagsCallback aCallback) +void ProfileBuffer::IterateTagsForThread(IterateTagsCallback aCallback, int aThreadId) { MOZ_ASSERT(aCallback); int readPos = mReadPos; - while (readPos != mLastFlushPos) { - // Number of tag consumed - int incBy = 1; + int currentThreadID = -1; + + while (readPos != mWritePos) { const ProfileEntry& entry = mEntries[readPos]; + if (entry.mTagName == 'T') { + currentThreadID = entry.mTagInt; + readPos = (readPos + 1) % mEntrySize; + continue; + } + + // Number of tags consumed + int incBy = 1; + // Read ahead to the next tag, if it's a 'd' tag process it now const char* tagStringData = entry.mTagData; int readAheadPos = (readPos + 1) % mEntrySize; @@ -300,47 +233,31 @@ void ThreadProfile::IterateTags(IterateTagsCallback aCallback) // Make sure the string is always null terminated if it fills up DYNAMIC_MAX_STRING-2 tagBuff[DYNAMIC_MAX_STRING-1] = '\0'; - if (readAheadPos != mLastFlushPos && mEntries[readAheadPos].mTagName == 'd') { + if (readAheadPos != mWritePos && mEntries[readAheadPos].mTagName == 'd') { tagStringData = processDynamicTag(readPos, &incBy, tagBuff); } - aCallback(entry, tagStringData); + if (currentThreadID == aThreadId) { + aCallback(entry, tagStringData); + } readPos = (readPos + incBy) % mEntrySize; } } -void ThreadProfile::ToStreamAsJSON(std::ostream& stream) +void ProfileBuffer::StreamSamplesToJSObject(JSStreamWriter& b, int aThreadId) { - JSStreamWriter b(stream); - StreamJSObject(b); -} - -void ThreadProfile::StreamJSObject(JSStreamWriter& b) -{ - b.BeginObject(); - // Thread meta data - if (XRE_GetProcessType() == GeckoProcessType_Plugin) { - // TODO Add the proper plugin name - b.NameValue("name", "Plugin"); - } else if (XRE_GetProcessType() == GeckoProcessType_Content) { - // This isn't going to really help once we have multiple content - // processes, but it'll do for now. - b.NameValue("name", "Content"); - } else { - b.NameValue("name", Name()); - } - b.NameValue("tid", static_cast(mThreadId)); - - b.Name("samples"); - b.BeginArray(); - - bool sample = false; - int readPos = mReadPos; - while (readPos != mLastFlushPos) { - // Number of tag consumed - ProfileEntry entry = mEntries[readPos]; + b.BeginArray(); + bool sample = false; + int readPos = mReadPos; + int currentThreadID = -1; + while (readPos != mWritePos) { + ProfileEntry entry = mEntries[readPos]; + if (entry.mTagName == 'T') { + currentThreadID = entry.mTagInt; + } + if (currentThreadID == aThreadId) { switch (entry.mTagName) { case 'r': { @@ -407,7 +324,7 @@ void ThreadProfile::StreamJSObject(JSStreamWriter& b) int framePos = (readPos + 1) % mEntrySize; ProfileEntry frame = mEntries[framePos]; - while (framePos != mLastFlushPos && frame.mTagName != 's') { + while (framePos != mWritePos && frame.mTagName != 's' && frame.mTagName != 'T') { int incBy = 1; frame = mEntries[framePos]; @@ -419,7 +336,7 @@ void ThreadProfile::StreamJSObject(JSStreamWriter& b) // DYNAMIC_MAX_STRING-2 tagBuff[DYNAMIC_MAX_STRING-1] = '\0'; - if (readAheadPos != mLastFlushPos && mEntries[readAheadPos].mTagName == 'd') { + if (readAheadPos != mWritePos && mEntries[readAheadPos].mTagName == 'd') { tagStringData = processDynamicTag(framePos, &incBy, tagBuff); } @@ -440,13 +357,13 @@ void ThreadProfile::StreamJSObject(JSStreamWriter& b) b.BeginObject(); b.NameValue("location", tagStringData); readAheadPos = (framePos + incBy) % mEntrySize; - if (readAheadPos != mLastFlushPos && + if (readAheadPos != mWritePos && mEntries[readAheadPos].mTagName == 'n') { b.NameValue("line", mEntries[readAheadPos].mTagInt); incBy++; } readAheadPos = (framePos + incBy) % mEntrySize; - if (readAheadPos != mLastFlushPos && + if (readAheadPos != mWritePos && mEntries[readAheadPos].mTagName == 'y') { b.NameValue("category", mEntries[readAheadPos].mTagInt); incBy++; @@ -459,24 +376,176 @@ void ThreadProfile::StreamJSObject(JSStreamWriter& b) } break; } - readPos = (readPos + 1) % mEntrySize; } - if (sample) { - b.EndObject(); + readPos = (readPos + 1) % mEntrySize; + } + if (sample) { + b.EndObject(); + } + b.EndArray(); +} + +void ProfileBuffer::StreamMarkersToJSObject(JSStreamWriter& b, int aThreadId) +{ + b.BeginArray(); + int readPos = mReadPos; + int currentThreadID = -1; + while (readPos != mWritePos) { + ProfileEntry entry = mEntries[readPos]; + if (entry.mTagName == 'T') { + currentThreadID = entry.mTagInt; + } else if (currentThreadID == aThreadId && entry.mTagName == 'm') { + entry.getMarker()->StreamJSObject(b); } - b.EndArray(); + readPos = (readPos + 1) % mEntrySize; + } + b.EndArray(); +} + +int ProfileBuffer::FindLastSampleOfThread(int aThreadId) +{ + // We search backwards from mWritePos-1 to mReadPos. + // Adding mEntrySize makes the result of the modulus positive. + for (int readPos = (mWritePos + mEntrySize - 1) % mEntrySize; + readPos != (mReadPos + mEntrySize - 1) % mEntrySize; + readPos = (readPos + mEntrySize - 1) % mEntrySize) { + ProfileEntry entry = mEntries[readPos]; + if (entry.mTagName == 'T' && entry.mTagInt == aThreadId) { + return readPos; + } + } + + return -1; +} + +void ProfileBuffer::DuplicateLastSample(int aThreadId) +{ + int lastSampleStartPos = FindLastSampleOfThread(aThreadId); + if (lastSampleStartPos == -1) { + return; + } + + MOZ_ASSERT(mEntries[lastSampleStartPos].mTagName == 'T'); + + addTag(mEntries[lastSampleStartPos]); + + // Go through the whole entry and duplicate it, until we find the next one. + for (int readPos = (lastSampleStartPos + 1) % mEntrySize; + readPos != mWritePos; + readPos = (readPos + 1) % mEntrySize) { + switch (mEntries[readPos].mTagName) { + case 'T': + // We're done. + return; + case 't': + // Copy with new time + addTag(ProfileEntry('t', static_cast((mozilla::TimeStamp::Now() - sStartTime).ToMilliseconds()))); + break; + case 'm': + // Don't copy markers + break; + // Copy anything else we don't know about + // L, B, S, c, s, d, l, f, h, r, t, p + default: + addTag(mEntries[readPos]); + break; + } + } +} + +std::ostream& +ProfileBuffer::StreamToOStream(std::ostream& stream, int aThreadId) const +{ + int readPos = mReadPos; + int currentThreadID = -1; + while (readPos != mWritePos) { + ProfileEntry entry = mEntries[readPos]; + if (entry.mTagName == 'T') { + currentThreadID = entry.mTagInt; + } else if (currentThreadID == aThreadId) { + stream << mEntries[readPos]; + } + readPos = (readPos + 1) % mEntrySize; + } + return stream; +} + +// END ProfileBuffer +//////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////// +// BEGIN ThreadProfile + +ThreadProfile::ThreadProfile(ThreadInfo* aInfo, ProfileBuffer* aBuffer) + : mThreadInfo(aInfo) + , mBuffer(aBuffer) + , mPseudoStack(aInfo->Stack()) + , mMutex("ThreadProfile::mMutex") + , mThreadId(int(aInfo->ThreadId())) + , mIsMainThread(aInfo->IsMainThread()) + , mPlatformData(aInfo->GetPlatformData()) + , mStackTop(aInfo->StackTop()) + , mRespInfo(this) +#ifdef XP_LINUX + , mRssMemory(0) + , mUssMemory(0) +#endif +{ + MOZ_COUNT_CTOR(ThreadProfile); + MOZ_ASSERT(aBuffer); + + // I don't know if we can assert this. But we should warn. + MOZ_ASSERT(aInfo->ThreadId() >= 0, "native thread ID is < 0"); + MOZ_ASSERT(aInfo->ThreadId() <= INT32_MAX, "native thread ID is > INT32_MAX"); +} + +ThreadProfile::~ThreadProfile() +{ + MOZ_COUNT_DTOR(ThreadProfile); +} + +void ThreadProfile::addTag(const ProfileEntry& aTag) +{ + mBuffer->addTag(aTag); +} + +void ThreadProfile::addStoredMarker(ProfilerMarker *aStoredMarker) { + mBuffer->addStoredMarker(aStoredMarker); +} + +void ThreadProfile::IterateTags(IterateTagsCallback aCallback) +{ + mBuffer->IterateTagsForThread(aCallback, mThreadId); +} + +void ThreadProfile::ToStreamAsJSON(std::ostream& stream) +{ + JSStreamWriter b(stream); + StreamJSObject(b); +} + +void ThreadProfile::StreamJSObject(JSStreamWriter& b) +{ + b.BeginObject(); + // Thread meta data + if (XRE_GetProcessType() == GeckoProcessType_Plugin) { + // TODO Add the proper plugin name + b.NameValue("name", "Plugin"); + } else if (XRE_GetProcessType() == GeckoProcessType_Content) { + // This isn't going to really help once we have multiple content + // processes, but it'll do for now. + b.NameValue("name", "Content"); + } else { + b.NameValue("name", Name()); + } + b.NameValue("tid", static_cast(mThreadId)); + + b.Name("samples"); + mBuffer->StreamSamplesToJSObject(b, mThreadId); b.Name("markers"); - b.BeginArray(); - readPos = mReadPos; - while (readPos != mLastFlushPos) { - ProfileEntry entry = mEntries[readPos]; - if (entry.mTagName == 'm') { - entry.getMarker()->StreamJSObject(b); - } - readPos = (readPos + 1) % mEntrySize; - } - b.EndArray(); + mBuffer->StreamMarkersToJSObject(b, mThreadId); b.EndObject(); } @@ -516,46 +585,14 @@ mozilla::Mutex* ThreadProfile::GetMutex() return &mMutex; } -void ThreadProfile::DuplicateLastSample() { - // Scan the whole buffer (even unflushed parts) - // Adding mEntrySize makes the result of the modulus positive - // We search backwards from mWritePos-1 to mReadPos - for (int readPos = (mWritePos + mEntrySize - 1) % mEntrySize; - readPos != (mReadPos + mEntrySize - 1) % mEntrySize; - readPos = (readPos + mEntrySize - 1) % mEntrySize) { - if (mEntries[readPos].mTagName == 's') { - // Found the start of the last entry at position readPos - int copyEndIdx = mWritePos; - // Go through the whole entry and duplicate it - for (;readPos != copyEndIdx; readPos = (readPos + 1) % mEntrySize) { - switch (mEntries[readPos].mTagName) { - // Copy with new time - case 't': - addTag(ProfileEntry('t', static_cast((mozilla::TimeStamp::Now() - sStartTime).ToMilliseconds()))); - break; - // Don't copy markers - case 'm': - break; - // Copy anything else we don't know about - // L, B, S, c, s, d, l, f, h, r, t, p - default: - addTag(mEntries[readPos]); - break; - } - } - break; - } - } +void ThreadProfile::DuplicateLastSample() +{ + mBuffer->DuplicateLastSample(mThreadId); } std::ostream& operator<<(std::ostream& stream, const ThreadProfile& profile) { - int readPos = profile.mReadPos; - while (readPos != profile.mLastFlushPos) { - stream << profile.mEntries[readPos]; - readPos = (readPos + 1) % profile.mEntrySize; - } - return stream; + return profile.mBuffer->StreamToOStream(stream, profile.mThreadId); } // END ThreadProfile diff --git a/tools/profiler/ProfileEntry.h b/tools/profiler/ProfileEntry.h index da9b15a2df6..36f93f2e166 100644 --- a/tools/profiler/ProfileEntry.h +++ b/tools/profiler/ProfileEntry.h @@ -12,8 +12,10 @@ #include "platform.h" #include "JSStreamWriter.h" #include "ProfilerBacktrace.h" +#include "nsRefPtr.h" #include "mozilla/Mutex.h" #include "gtest/MozGtestFriend.h" +#include "mozilla/UniquePtr.h" class ThreadProfile; @@ -52,7 +54,7 @@ private: FRIEND_TEST(ThreadProfile, InsertTagsNoWrap); FRIEND_TEST(ThreadProfile, InsertTagsWrap); FRIEND_TEST(ThreadProfile, MemoryMeasure); - friend class ThreadProfile; + friend class ProfileBuffer; union { const char* mTagData; char mTagChars[sizeof(void*)]; @@ -71,15 +73,64 @@ private: typedef void (*IterateTagsCallback)(const ProfileEntry& entry, const char* tagStringData); +class ProfileBuffer { +public: + NS_INLINE_DECL_REFCOUNTING(ProfileBuffer) + + explicit ProfileBuffer(int aEntrySize); + + void addTag(const ProfileEntry& aTag); + void IterateTagsForThread(IterateTagsCallback aCallback, int aThreadId); + void StreamSamplesToJSObject(JSStreamWriter& b, int aThreadId); + void StreamMarkersToJSObject(JSStreamWriter& b, int aThreadId); + void DuplicateLastSample(int aThreadId); + + void addStoredMarker(ProfilerMarker* aStoredMarker); + void deleteExpiredStoredMarkers(); + + std::ostream& StreamToOStream(std::ostream& stream, int aThreadId) const; + +protected: + char* processDynamicTag(int readPos, int* tagsConsumed, char* tagBuff); + int FindLastSampleOfThread(int aThreadId); + + ~ProfileBuffer() {} + +public: + // Circular buffer 'Keep One Slot Open' implementation for simplicity + mozilla::UniquePtr mEntries; + + // Points to the next entry we will write to, which is also the one at which + // we need to stop reading. + int mWritePos; + + // Points to the entry at which we can start reading. + int mReadPos; + + // The number of entries in our buffer. + int mEntrySize; + + // How many times mWritePos has wrapped around. + int mGeneration; + + // Markers that marker entries in the buffer might refer to. + ProfilerMarkerLinkedList mStoredMarkers; +}; + class ThreadProfile { public: - ThreadProfile(ThreadInfo* aThreadInfo, int aEntrySize); + ThreadProfile(ThreadInfo* aThreadInfo, ProfileBuffer* aBuffer); virtual ~ThreadProfile(); - void addTag(ProfileEntry aTag); - void flush(); - void erase(); - char* processDynamicTag(int readPos, int* tagsConsumed, char* tagBuff); + void addTag(const ProfileEntry& aTag); + + /** + * Track a marker which has been inserted into the ThreadProfile. + * This marker can safely be deleted once the generation has + * expired. + */ + void addStoredMarker(ProfilerMarker *aStoredMarker); + void IterateTags(IterateTagsCallback aCallback); friend std::ostream& operator<<(std::ostream& stream, const ThreadProfile& profile); @@ -94,13 +145,9 @@ public: bool IsMainThread() const { return mIsMainThread; } const char* Name() const { return mThreadInfo->Name(); } - Thread::tid_t ThreadId() const { return mThreadId; } + int ThreadId() const { return mThreadId; } PlatformData* GetPlatformData() const { return mPlatformData; } - int GetGenerationID() const { return mGeneration; } - bool HasGenerationExpired(int aGenID) const { - return aGenID + 2 <= mGeneration; - } void* GetStackTop() const { return mStackTop; } void DuplicateLastSample(); @@ -118,20 +165,14 @@ private: FRIEND_TEST(ThreadProfile, InsertTagsWrap); FRIEND_TEST(ThreadProfile, MemoryMeasure); ThreadInfo* mThreadInfo; - // Circular buffer 'Keep One Slot Open' implementation - // for simplicity - ProfileEntry* mEntries; - int mWritePos; // points to the next entry we will write to - int mLastFlushPos; // points to the next entry since the last flush() - int mReadPos; // points to the next entry we will read to - int mEntrySize; + + const nsRefPtr mBuffer; + PseudoStack* mPseudoStack; mozilla::Mutex mMutex; - Thread::tid_t mThreadId; + int mThreadId; bool mIsMainThread; PlatformData* mPlatformData; // Platform specific data. - int mGeneration; - int mPendingGenerationFlush; void* const mStackTop; ThreadResponsiveness mRespInfo; diff --git a/tools/profiler/PseudoStack.h b/tools/profiler/PseudoStack.h index 6beaf4efce8..96859a1f227 100644 --- a/tools/profiler/PseudoStack.h +++ b/tools/profiler/PseudoStack.h @@ -176,90 +176,62 @@ private: typedef ProfilerLinkedList ProfilerMarkerLinkedList; typedef ProfilerLinkedList UWTBufferLinkedList; -class PendingMarkers { +template +class ProfilerSignalSafeLinkedList { public: - PendingMarkers() + ProfilerSignalSafeLinkedList() : mSignalLock(false) {} - ~PendingMarkers(); - - void addMarker(ProfilerMarker *aMarker); - - void updateGeneration(int aGenID); - - /** - * Track a marker which has been inserted into the ThreadProfile. - * This marker can safely be deleted once the generation has - * expired. - */ - void addStoredMarker(ProfilerMarker *aStoredMarker); - - // called within signal. Function must be reentrant - ProfilerMarkerLinkedList* getPendingMarkers() + ~ProfilerSignalSafeLinkedList() { - // if mSignalLock then the stack is inconsistent because it's being - // modified by the profiled thread. Post pone these markers - // for the next sample. The odds of a livelock are nearly impossible - // and would show up in a profile as many sample in 'addMarker' thus - // we ignore this scenario. if (mSignalLock) { - return nullptr; + // Some thread is modifying the list. We should only be released on that + // thread. + abort(); } - return &mPendingMarkers; - } - void clearMarkers() - { - while (mPendingMarkers.peek()) { - delete mPendingMarkers.popHead(); - } - while (mStoredMarkers.peek()) { - delete mStoredMarkers.popHead(); + while (mList.peek()) { + delete mList.popHead(); } } -private: - // Keep a list of active markers to be applied to the next sample taken - ProfilerMarkerLinkedList mPendingMarkers; - ProfilerMarkerLinkedList mStoredMarkers; - // If this is set then it's not safe to read mStackPointer from the signal handler - volatile bool mSignalLock; - // We don't want to modify _markers from within the signal so we allow - // it to queue a clear operation. - volatile mozilla::sig_safe_t mGenID; -}; + // Insert an item into the list. + // Must only be called from the owning thread. + // Must not be called while the list from accessList() is being accessed. + // In the profiler, we ensure that by interrupting the profiled thread + // (which is the one that owns this list and calls insert() on it) until + // we're done reading the list from the signal handler. + void insert(T* aElement) { + MOZ_ASSERT(aElement); -class PendingUWTBuffers -{ -public: - PendingUWTBuffers() - : mSignalLock(false) - { - } - - void addLinkedUWTBuffer(LinkedUWTBuffer* aBuff) - { - MOZ_ASSERT(aBuff); mSignalLock = true; STORE_SEQUENCER(); - mPendingUWTBuffers.insert(aBuff); + + mList.insert(aElement); + STORE_SEQUENCER(); mSignalLock = false; } - // called within signal. Function must be reentrant - UWTBufferLinkedList* getLinkedUWTBuffers() + // Called within signal, from any thread, possibly while insert() is in the + // middle of modifying the list (on the owning thread). Will return null if + // that is the case. + // Function must be reentrant. + ProfilerLinkedList* accessList() { if (mSignalLock) { return nullptr; } - return &mPendingUWTBuffers; + return &mList; } private: - UWTBufferLinkedList mPendingUWTBuffers; - volatile bool mSignalLock; + ProfilerLinkedList mList; + + // If this is set, then it's not safe to read the list because its contents + // are being changed. + volatile bool mSignalLock; }; // Stub eventMarker function for js-engine event generation. @@ -285,32 +257,27 @@ public: void addLinkedUWTBuffer(LinkedUWTBuffer* aBuff) { - mPendingUWTBuffers.addLinkedUWTBuffer(aBuff); + mPendingUWTBuffers.insert(aBuff); } UWTBufferLinkedList* getLinkedUWTBuffers() { - return mPendingUWTBuffers.getLinkedUWTBuffers(); + return mPendingUWTBuffers.accessList(); } void addMarker(const char *aMarkerStr, ProfilerMarkerPayload *aPayload, float aTime) { ProfilerMarker* marker = new ProfilerMarker(aMarkerStr, aPayload, aTime); - mPendingMarkers.addMarker(marker); - } - - void addStoredMarker(ProfilerMarker *aStoredMarker) { - mPendingMarkers.addStoredMarker(aStoredMarker); - } - - void updateGeneration(int aGenID) { - mPendingMarkers.updateGeneration(aGenID); + mPendingMarkers.insert(marker); } // called within signal. Function must be reentrant ProfilerMarkerLinkedList* getPendingMarkers() { - return mPendingMarkers.getPendingMarkers(); + // The profiled thread is interrupted, so we can access the list safely. + // Unless the profiled thread was in the middle of changing the list when + // we interrupted it - in that case, accessList() will return null. + return mPendingMarkers.accessList(); } void push(const char *aName, js::ProfileEntry::Category aCategory, uint32_t line) @@ -450,9 +417,9 @@ public: // Keep a list of pending markers that must be moved // to the circular buffer - PendingMarkers mPendingMarkers; + ProfilerSignalSafeLinkedList mPendingMarkers; // List of LinkedUWTBuffers that must be processed on the next tick - PendingUWTBuffers mPendingUWTBuffers; + ProfilerSignalSafeLinkedList mPendingUWTBuffers; // This may exceed the length of mStack, so instead use the stackSize() method // to determine the number of valid samples in mStack mozilla::sig_safe_t mStackPointer; diff --git a/tools/profiler/SyncProfile.cpp b/tools/profiler/SyncProfile.cpp index d7190b280b6..b6e3029f493 100644 --- a/tools/profiler/SyncProfile.cpp +++ b/tools/profiler/SyncProfile.cpp @@ -8,7 +8,7 @@ #include "UnwinderThread2.h" SyncProfile::SyncProfile(ThreadInfo* aInfo, int aEntrySize) - : ThreadProfile(aInfo, aEntrySize) + : ThreadProfile(aInfo, new ProfileBuffer(aEntrySize)) , mOwnerState(REFERENCED) , mUtb(nullptr) { @@ -57,7 +57,6 @@ SyncProfile::EndUnwind() utb__end_sync_buffer_unwind(mUtb); } if (mOwnerState != ORPHANED) { - flush(); mOwnerState = OWNED; } // Save mOwnerState before we release the mutex diff --git a/tools/profiler/TableTicker.cpp b/tools/profiler/TableTicker.cpp index cc60059054d..a6ffa013ef1 100644 --- a/tools/profiler/TableTicker.cpp +++ b/tools/profiler/TableTicker.cpp @@ -107,6 +107,10 @@ void TableTicker::HandleSaveRequest() NS_DispatchToMainThread(runnable); } +void TableTicker::DeleteExpiredMarkers() +{ + mBuffer->deleteExpiredStoredMarkers(); +} void TableTicker::StreamTaskTracer(JSStreamWriter& b) { @@ -153,7 +157,6 @@ void TableTicker::StreamMetaJSCustomObject(JSStreamWriter& b) b.NameValue("version", 2); b.NameValue("interval", interval()); b.NameValue("stackwalk", mUseStackWalk); - b.NameValue("jank", mJankOnly); b.NameValue("processType", XRE_GetProcessType()); mozilla::TimeDuration delta = mozilla::TimeStamp::Now() - sStartTime; @@ -753,52 +756,9 @@ void TableTicker::InplaceTick(TickSample* sample) { ThreadProfile& currThreadProfile = *sample->threadProfile; + currThreadProfile.addTag(ProfileEntry('T', currThreadProfile.ThreadId())); + PseudoStack* stack = currThreadProfile.GetPseudoStack(); - stack->updateGeneration(currThreadProfile.GetGenerationID()); - bool recordSample = true; -#if defined(XP_WIN) - bool powerSample = false; -#endif - - /* Don't process the PeudoStack's markers or honour jankOnly if we're - immediately sampling the current thread. */ - if (!sample->isSamplingCurrentThread) { - // Marker(s) come before the sample - ProfilerMarkerLinkedList* pendingMarkersList = stack->getPendingMarkers(); - while (pendingMarkersList && pendingMarkersList->peek()) { - ProfilerMarker* marker = pendingMarkersList->popHead(); - stack->addStoredMarker(marker); - currThreadProfile.addTag(ProfileEntry('m', marker)); - } - -#if defined(XP_WIN) - if (mProfilePower) { - mIntelPowerGadget->TakeSample(); - powerSample = true; - } -#endif - - if (mJankOnly) { - // if we are on a different event we can discard any temporary samples - // we've kept around - if (sLastSampledEventGeneration != sCurrentEventGeneration) { - // XXX: we also probably want to add an entry to the profile to help - // distinguish which samples are part of the same event. That, or record - // the event generation in each sample - currThreadProfile.erase(); - } - sLastSampledEventGeneration = sCurrentEventGeneration; - - recordSample = false; - // only record the events when we have a we haven't seen a tracer event for 100ms - if (!sLastTracerEvent.IsNull()) { - mozilla::TimeDuration delta = sample->timestamp - sLastTracerEvent; - if (delta.ToMilliseconds() > 100.0) { - recordSample = true; - } - } - } - } #if defined(USE_NS_STACKWALK) || defined(USE_EHABI_STACKWALK) if (mUseStackWalk) { @@ -810,8 +770,16 @@ void TableTicker::InplaceTick(TickSample* sample) doSampleStackTrace(currThreadProfile, sample, mAddLeafAddresses); #endif - if (recordSample) - currThreadProfile.flush(); + // Don't process the PeudoStack's markers if we're + // synchronously sampling the current thread. + if (!sample->isSamplingCurrentThread) { + ProfilerMarkerLinkedList* pendingMarkersList = stack->getPendingMarkers(); + while (pendingMarkersList && pendingMarkersList->peek()) { + ProfilerMarker* marker = pendingMarkersList->popHead(); + currThreadProfile.addStoredMarker(marker); + currThreadProfile.addTag(ProfileEntry('m', marker)); + } + } if (sample && currThreadProfile.GetThreadResponsiveness()->HasData()) { mozilla::TimeDuration delta = currThreadProfile.GetThreadResponsiveness()->GetUnresponsiveDuration(sample->timestamp); @@ -834,7 +802,8 @@ void TableTicker::InplaceTick(TickSample* sample) } #if defined(XP_WIN) - if (powerSample) { + if (mProfilePower) { + mIntelPowerGadget->TakeSample(); currThreadProfile.addTag(ProfileEntry('p', static_cast(mIntelPowerGadget->GetTotalPackagePowerInWatts()))); } #endif diff --git a/tools/profiler/TableTicker.h b/tools/profiler/TableTicker.h index 09c888f9315..83f80b4487f 100644 --- a/tools/profiler/TableTicker.h +++ b/tools/profiler/TableTicker.h @@ -42,8 +42,6 @@ threadSelected(ThreadInfo* aInfo, char** aThreadNameFilters, uint32_t aFeatureCo extern mozilla::TimeStamp sLastTracerEvent; extern int sFrameNumber; extern int sLastFrameNumber; -extern unsigned int sCurrentEventGeneration; -extern unsigned int sLastSampledEventGeneration; class BreakpadSampler; @@ -54,6 +52,7 @@ class TableTicker: public Sampler { const char** aThreadNameFilters, uint32_t aFilterCount) : Sampler(aInterval, true, aEntrySize) , mPrimaryThreadProfile(nullptr) + , mBuffer(new ProfileBuffer(aEntrySize)) , mSaveRequested(false) , mUnwinderThread(false) , mFilterCount(aFilterCount) @@ -63,8 +62,6 @@ class TableTicker: public Sampler { { mUseStackWalk = hasFeature(aFeatures, aFeatureCount, "stackwalk"); - //XXX: It's probably worth splitting the jank profiler out from the regular profiler at some point - mJankOnly = hasFeature(aFeatures, aFeatureCount, "jank"); mProfileJS = hasFeature(aFeatures, aFeatureCount, "js"); mProfileJava = hasFeature(aFeatures, aFeatureCount, "java"); mProfileGPU = hasFeature(aFeatures, aFeatureCount, "gpu"); @@ -156,7 +153,7 @@ class TableTicker: public Sampler { return; } - ThreadProfile* profile = new ThreadProfile(aInfo, EntrySize()); + ThreadProfile* profile = new ThreadProfile(aInfo, mBuffer); aInfo->SetProfile(profile); } @@ -178,6 +175,7 @@ class TableTicker: public Sampler { } virtual void HandleSaveRequest(); + virtual void DeleteExpiredMarkers() MOZ_OVERRIDE; ThreadProfile* GetPrimaryThreadProfile() { @@ -227,10 +225,10 @@ protected: // This represent the application's main thread (SAMPLER_INIT) ThreadProfile* mPrimaryThreadProfile; + nsRefPtr mBuffer; bool mSaveRequested; bool mAddLeafAddresses; bool mUseStackWalk; - bool mJankOnly; bool mProfileJS; bool mProfileGPU; bool mProfileThreads; diff --git a/tools/profiler/UnwinderThread2.cpp b/tools/profiler/UnwinderThread2.cpp index 166d1b0be81..9a8916b450a 100644 --- a/tools/profiler/UnwinderThread2.cpp +++ b/tools/profiler/UnwinderThread2.cpp @@ -1186,7 +1186,7 @@ static void process_buffer(UnwinderThreadBuffer* buff, int oldest_ix) for (k = 0; k < buff->entsUsed; k++) { ProfileEntry ent = utb_get_profent(buff, k); // action flush-hints - if (ent.is_ent_hint('F')) { buff->aProfile->flush(); continue; } + if (ent.is_ent_hint('F')) { continue; } // skip ones we can't copy if (ent.is_ent_hint() || ent.is_ent('S')) { continue; } // handle GetBacktrace() @@ -1223,7 +1223,7 @@ static void process_buffer(UnwinderThreadBuffer* buff, int oldest_ix) continue; } // action flush-hints - if (ent.is_ent_hint('F')) { buff->aProfile->flush(); continue; } + if (ent.is_ent_hint('F')) { continue; } // skip ones we can't copy if (ent.is_ent_hint() || ent.is_ent('S')) { continue; } // handle GetBacktrace() @@ -1249,7 +1249,7 @@ static void process_buffer(UnwinderThreadBuffer* buff, int oldest_ix) buff->aProfile->addTag( ProfileEntry('s', "(root)") ); } // action flush-hints - if (ent.is_ent_hint('F')) { buff->aProfile->flush(); continue; } + if (ent.is_ent_hint('F')) { continue; } // skip ones we can't copy if (ent.is_ent_hint() || ent.is_ent('S')) { continue; } // handle GetBacktrace() @@ -1281,7 +1281,7 @@ static void process_buffer(UnwinderThreadBuffer* buff, int oldest_ix) for (k = 0; k < ix_first_hP; k++) { ProfileEntry ent = utb_get_profent(buff, k); // action flush-hints - if (ent.is_ent_hint('F')) { buff->aProfile->flush(); continue; } + if (ent.is_ent_hint('F')) { continue; } // skip ones we can't copy if (ent.is_ent_hint() || ent.is_ent('S')) { continue; } // handle GetBacktrace() @@ -1400,7 +1400,7 @@ static void process_buffer(UnwinderThreadBuffer* buff, int oldest_ix) for (k = ix_last_hQ+1; k < buff->entsUsed; k++) { ProfileEntry ent = utb_get_profent(buff, k); // action flush-hints - if (ent.is_ent_hint('F')) { buff->aProfile->flush(); continue; } + if (ent.is_ent_hint('F')) { continue; } // skip ones we can't copy if (ent.is_ent_hint() || ent.is_ent('S')) { continue; } // and copy everything else @@ -1418,11 +1418,7 @@ static void process_buffer(UnwinderThreadBuffer* buff, int oldest_ix) for (k = 0; k < buff->entsUsed; k++) { ProfileEntry ent = utb_get_profent(buff, k); if (show) ent.log(); - if (ent.is_ent_hint('F')) { - /* This is a flush-hint */ - buff->aProfile->flush(); - } - else if (ent.is_ent_hint('N')) { + if (ent.is_ent_hint('N')) { /* This is a do-a-native-unwind-right-now hint */ MOZ_ASSERT(buff->haveNativeInfo); PCandSP* pairs = nullptr; diff --git a/tools/profiler/platform-linux.cc b/tools/profiler/platform-linux.cc index e13fbaa153d..2ea2534bdee 100644 --- a/tools/profiler/platform-linux.cc +++ b/tools/profiler/platform-linux.cc @@ -294,6 +294,7 @@ static void* SignalSender(void* arg) { while (SamplerRegistry::sampler->IsActive()) { SamplerRegistry::sampler->HandleSaveRequest(); + SamplerRegistry::sampler->DeleteExpiredMarkers(); if (!SamplerRegistry::sampler->IsPaused()) { mozilla::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex); @@ -311,8 +312,6 @@ static void* SignalSender(void* arg) { PseudoStack::SleepState sleeping = info->Stack()->observeSleeping(); if (sleeping == PseudoStack::SLEEPING_AGAIN) { info->Profile()->DuplicateLastSample(); - //XXX: This causes flushes regardless of jank-only mode - info->Profile()->flush(); continue; } diff --git a/tools/profiler/platform-macos.cc b/tools/profiler/platform-macos.cc index 003d960fd05..bcd91f76130 100644 --- a/tools/profiler/platform-macos.cc +++ b/tools/profiler/platform-macos.cc @@ -203,6 +203,7 @@ class SamplerThread : public Thread { // Implement Thread::Run(). virtual void Run() { while (SamplerRegistry::sampler->IsActive()) { + SamplerRegistry::sampler->DeleteExpiredMarkers(); if (!SamplerRegistry::sampler->IsPaused()) { mozilla::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex); std::vector threads = @@ -218,8 +219,6 @@ class SamplerThread : public Thread { PseudoStack::SleepState sleeping = info->Stack()->observeSleeping(); if (sleeping == PseudoStack::SLEEPING_AGAIN) { info->Profile()->DuplicateLastSample(); - //XXX: This causes flushes regardless of jank-only mode - info->Profile()->flush(); continue; } diff --git a/tools/profiler/platform-win32.cc b/tools/profiler/platform-win32.cc index c20c45b7c5c..289c21f4630 100644 --- a/tools/profiler/platform-win32.cc +++ b/tools/profiler/platform-win32.cc @@ -120,6 +120,8 @@ class SamplerThread : public Thread { ::timeBeginPeriod(interval_); while (sampler_->IsActive()) { + sampler_->DeleteExpiredMarkers(); + if (!sampler_->IsPaused()) { mozilla::MutexAutoLock lock(*Sampler::sRegisteredThreadsMutex); std::vector threads = @@ -135,8 +137,6 @@ class SamplerThread : public Thread { PseudoStack::SleepState sleeping = info->Stack()->observeSleeping(); if (sleeping == PseudoStack::SLEEPING_AGAIN) { info->Profile()->DuplicateLastSample(); - //XXX: This causes flushes regardless of jank-only mode - info->Profile()->flush(); continue; } diff --git a/tools/profiler/platform.cpp b/tools/profiler/platform.cpp index 0e3237d44e1..eb17bd5b93f 100644 --- a/tools/profiler/platform.cpp +++ b/tools/profiler/platform.cpp @@ -56,13 +56,6 @@ const char* PROFILER_ENTRIES = "MOZ_PROFILER_ENTRIES"; const char* PROFILER_STACK = "MOZ_PROFILER_STACK_SCAN"; const char* PROFILER_FEATURES = "MOZ_PROFILING_FEATURES"; -/* used to keep track of the last event that we sampled during */ -unsigned int sLastSampledEventGeneration = 0; - -/* a counter that's incremented everytime we get responsiveness event - * note: it might also be worth trackplaing everytime we go around - * the event loop */ -unsigned int sCurrentEventGeneration = 0; /* we don't need to worry about overflow because we only treat the * case of them being the same as special. i.e. we only run into * a problem if 2^32 events happen between samples that we need @@ -203,45 +196,6 @@ void ProfilerMarker::StreamJSObject(JSStreamWriter& b) const { b.EndObject(); } -PendingMarkers::~PendingMarkers() { - clearMarkers(); - if (mSignalLock != false) { - // We're releasing the pseudostack while it's still in use. - // The label macros keep a non ref counted reference to the - // stack to avoid a TLS. If these are not all cleared we will - // get a use-after-free so better to crash now. - abort(); - } -} - -void -PendingMarkers::addMarker(ProfilerMarker *aMarker) { - mSignalLock = true; - STORE_SEQUENCER(); - - MOZ_ASSERT(aMarker); - mPendingMarkers.insert(aMarker); - - // Clear markers that have been overwritten - while (mStoredMarkers.peek() && - mStoredMarkers.peek()->HasExpired(mGenID)) { - delete mStoredMarkers.popHead(); - } - STORE_SEQUENCER(); - mSignalLock = false; -} - -void -PendingMarkers::updateGeneration(int aGenID) { - mGenID = aGenID; -} - -void -PendingMarkers::addStoredMarker(ProfilerMarker *aStoredMarker) { - aStoredMarker->SetGeneration(mGenID); - mStoredMarkers.insert(aStoredMarker); -} - bool sps_version2() { static int version = 0; // Raced on, potentially @@ -939,8 +893,6 @@ bool mozilla_sampler_is_active() void mozilla_sampler_responsiveness(const mozilla::TimeStamp& aTime) { - sCurrentEventGeneration++; - sLastTracerEvent = aTime; } diff --git a/tools/profiler/platform.h b/tools/profiler/platform.h index 5e8a401cbf9..cf1f3928346 100644 --- a/tools/profiler/platform.h +++ b/tools/profiler/platform.h @@ -309,6 +309,8 @@ class Sampler { virtual void RequestSave() = 0; // Process any outstanding request outside a signal handler. virtual void HandleSaveRequest() = 0; + // Delete markers which are no longer part of the profile due to buffer wraparound. + virtual void DeleteExpiredMarkers() = 0; // Start and stop sampler. void Start(); diff --git a/tools/profiler/tests/gtest/ThreadProfileTest.cpp b/tools/profiler/tests/gtest/ThreadProfileTest.cpp index d8ac6d08918..a1a18877b23 100644 --- a/tools/profiler/tests/gtest/ThreadProfileTest.cpp +++ b/tools/profiler/tests/gtest/ThreadProfileTest.cpp @@ -12,7 +12,8 @@ TEST(ThreadProfile, Initialization) { PseudoStack* stack = PseudoStack::create(); Thread::tid_t tid = 1000; ThreadInfo info("testThread", tid, true, stack, nullptr); - ThreadProfile tp(&info, 10); + nsRefPtr pb = new ProfileBuffer(10); + ThreadProfile tp(&info, pb); } // Make sure we can record one tag and read it @@ -20,11 +21,11 @@ TEST(ThreadProfile, InsertOneTag) { PseudoStack* stack = PseudoStack::create(); Thread::tid_t tid = 1000; ThreadInfo info("testThread", tid, true, stack, nullptr); - ThreadProfile tp(&info, 10); - tp.addTag(ProfileEntry('t', 123.1f)); - ASSERT_TRUE(tp.mEntries != nullptr); - ASSERT_TRUE(tp.mEntries[tp.mReadPos].mTagName == 't'); - ASSERT_TRUE(tp.mEntries[tp.mReadPos].mTagFloat == 123.1f); + nsRefPtr pb = new ProfileBuffer(10); + pb->addTag(ProfileEntry('t', 123.1f)); + ASSERT_TRUE(pb->mEntries != nullptr); + ASSERT_TRUE(pb->mEntries[pb->mReadPos].mTagName == 't'); + ASSERT_TRUE(pb->mEntries[pb->mReadPos].mTagFloat == 123.1f); } // See if we can insert some tags @@ -32,17 +33,17 @@ TEST(ThreadProfile, InsertTagsNoWrap) { PseudoStack* stack = PseudoStack::create(); Thread::tid_t tid = 1000; ThreadInfo info("testThread", tid, true, stack, nullptr); - ThreadProfile tp(&info, 100); + nsRefPtr pb = new ProfileBuffer(100); int test_size = 50; for (int i = 0; i < test_size; i++) { - tp.addTag(ProfileEntry('t', i)); + pb->addTag(ProfileEntry('t', i)); } - ASSERT_TRUE(tp.mEntries != nullptr); - int readPos = tp.mReadPos; - while (readPos != tp.mWritePos) { - ASSERT_TRUE(tp.mEntries[readPos].mTagName == 't'); - ASSERT_TRUE(tp.mEntries[readPos].mTagInt == readPos); - readPos = (readPos + 1) % tp.mEntrySize; + ASSERT_TRUE(pb->mEntries != nullptr); + int readPos = pb->mReadPos; + while (readPos != pb->mWritePos) { + ASSERT_TRUE(pb->mEntries[readPos].mTagName == 't'); + ASSERT_TRUE(pb->mEntries[readPos].mTagInt == readPos); + readPos = (readPos + 1) % pb->mEntrySize; } } @@ -54,20 +55,20 @@ TEST(ThreadProfile, InsertTagsWrap) { int tags = 24; int buffer_size = tags + 1; ThreadInfo info("testThread", tid, true, stack, nullptr); - ThreadProfile tp(&info, buffer_size); + nsRefPtr pb = new ProfileBuffer(buffer_size); int test_size = 43; for (int i = 0; i < test_size; i++) { - tp.addTag(ProfileEntry('t', i)); + pb->addTag(ProfileEntry('t', i)); } - ASSERT_TRUE(tp.mEntries != nullptr); - int readPos = tp.mReadPos; + ASSERT_TRUE(pb->mEntries != nullptr); + int readPos = pb->mReadPos; int ctr = 0; - while (readPos != tp.mWritePos) { - ASSERT_TRUE(tp.mEntries[readPos].mTagName == 't'); + while (readPos != pb->mWritePos) { + ASSERT_TRUE(pb->mEntries[readPos].mTagName == 't'); // the first few tags were discarded when we wrapped - ASSERT_TRUE(tp.mEntries[readPos].mTagInt == ctr + (test_size - tags)); + ASSERT_TRUE(pb->mEntries[readPos].mTagInt == ctr + (test_size - tags)); ctr++; - readPos = (readPos + 1) % tp.mEntrySize; + readPos = (readPos + 1) % pb->mEntrySize; } } From 08f30afd37747123e684f96b7a148e2ebb0da8d6 Mon Sep 17 00:00:00 2001 From: Jeff Muizelaar Date: Fri, 30 Jan 2015 15:51:38 -0500 Subject: [PATCH 044/101] Bug 1127839. Inline some single use methods. r=kats These methods are only used from the constructors. --- mobile/android/base/gfx/BufferedImage.java | 25 ++++++---------------- 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/mobile/android/base/gfx/BufferedImage.java b/mobile/android/base/gfx/BufferedImage.java index ec81ebfd07f..9e63e3e59b9 100644 --- a/mobile/android/base/gfx/BufferedImage.java +++ b/mobile/android/base/gfx/BufferedImage.java @@ -22,12 +22,17 @@ public class BufferedImage { /** Creates an empty buffered image */ public BufferedImage() { - setBuffer(null, 0, 0, 0); + mSize = new IntSize(0, 0); } /** Creates a buffered image from an Android bitmap. */ public BufferedImage(Bitmap bitmap) { - setBitmap(bitmap); + mFormat = bitmapConfigToFormat(bitmap.getConfig()); + mSize = new IntSize(bitmap.getWidth(), bitmap.getHeight()); + + int bpp = bitsPerPixelForFormat(mFormat); + mBuffer = DirectBufferAllocator.allocate(mSize.getArea() * bpp); + bitmap.copyPixelsToBuffer(mBuffer.asIntBuffer()); } private synchronized void freeBuffer() { @@ -53,22 +58,6 @@ public class BufferedImage { public static final int FORMAT_A1 = 3; public static final int FORMAT_RGB16_565 = 4; - public void setBuffer(ByteBuffer buffer, int width, int height, int format) { - freeBuffer(); - mBuffer = buffer; - mSize = new IntSize(width, height); - mFormat = format; - } - - public void setBitmap(Bitmap bitmap) { - mFormat = bitmapConfigToFormat(bitmap.getConfig()); - mSize = new IntSize(bitmap.getWidth(), bitmap.getHeight()); - - int bpp = bitsPerPixelForFormat(mFormat); - mBuffer = DirectBufferAllocator.allocate(mSize.getArea() * bpp); - bitmap.copyPixelsToBuffer(mBuffer.asIntBuffer()); - } - private static int bitsPerPixelForFormat(int format) { switch (format) { case FORMAT_A1: return 1; From 690eece569362821876557825d5f9ed19b2e3bb7 Mon Sep 17 00:00:00 2001 From: Jan de Mooij Date: Fri, 30 Jan 2015 22:11:53 +0100 Subject: [PATCH 045/101] Bug 1126318 - Fix TryAttachNativeGetPropStub to ensure the function is native before using its jitinfo. r=bz --- js/src/jit/BaselineIC.cpp | 102 ++++++++++++------------- js/src/tests/ecma_5/misc/bug1126318.js | 13 ++++ 2 files changed, 63 insertions(+), 52 deletions(-) create mode 100644 js/src/tests/ecma_5/misc/bug1126318.js diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index 15b03907e64..527e8cf2b83 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -6539,6 +6539,10 @@ TryAttachNativeGetPropStub(JSContext *cx, HandleScript script, jsbytecode *pc, isTemporarilyUnoptimizable, isDOMProxy); } + // Try handling JSNative getters. + if (!cacheableCall || isScripted) + return true; + if (!shape || !shape->hasGetterValue() || !shape->getterValue().isObject() || !shape->getterObject()->is()) { @@ -6546,72 +6550,66 @@ TryAttachNativeGetPropStub(JSContext *cx, HandleScript script, jsbytecode *pc, } RootedFunction callee(cx, &shape->getterObject()->as()); + MOZ_ASSERT(callee->isNative()); if (outerClass && (!callee->jitInfo() || callee->jitInfo()->needsOuterizedThisObject())) return true; - // Try handling JSNative getters. - if (cacheableCall && !isScripted) { #if JS_HAS_NO_SUCH_METHOD - // It's unlikely that a getter function will be used to generate functions for calling - // in CALLPROP locations. Just don't attach stubs in that case to avoid issues with - // __noSuchMethod__ handling. - if (isCallProp) - return true; + // It's unlikely that a getter function will be used to generate functions for calling + // in CALLPROP locations. Just don't attach stubs in that case to avoid issues with + // __noSuchMethod__ handling. + if (isCallProp) + return true; #endif - MOZ_ASSERT(callee->isNative()); + JitSpew(JitSpew_BaselineIC, " Generating GetProp(%s%s/NativeGetter %p) stub", + isDOMProxy ? "DOMProxyObj" : "NativeObj", + isDOMProxy && domProxyHasGeneration ? "WithGeneration" : "", + callee->native()); - JitSpew(JitSpew_BaselineIC, " Generating GetProp(%s%s/NativeGetter %p) stub", - isDOMProxy ? "DOMProxyObj" : "NativeObj", - isDOMProxy && domProxyHasGeneration ? "WithGeneration" : "", - callee->native()); - - ICStub *newStub = nullptr; - if (isDOMProxy) { - MOZ_ASSERT(obj != holder); - ICStub::Kind kind; - if (domProxyHasGeneration) { - if (UpdateExistingGenerationalDOMProxyStub(stub, obj)) { - *attached = true; - return true; - } - kind = ICStub::GetProp_CallDOMProxyWithGenerationNative; - } else { - kind = ICStub::GetProp_CallDOMProxyNative; - } - Rooted proxy(cx, &obj->as()); - ICGetPropCallDOMProxyNativeCompiler - compiler(cx, kind, monitorStub, proxy, holder, callee, script->pcToOffset(pc)); - newStub = compiler.getStub(compiler.getStubSpace(script)); - } else if (obj == holder) { - if (UpdateExistingGetPropCallStubs(stub, ICStub::GetProp_CallNative, - obj, JS::NullPtr(), callee)) { + ICStub *newStub = nullptr; + if (isDOMProxy) { + MOZ_ASSERT(obj != holder); + ICStub::Kind kind; + if (domProxyHasGeneration) { + if (UpdateExistingGenerationalDOMProxyStub(stub, obj)) { *attached = true; return true; } - - ICGetProp_CallNative::Compiler compiler(cx, monitorStub, obj, callee, - script->pcToOffset(pc), outerClass); - newStub = compiler.getStub(compiler.getStubSpace(script)); + kind = ICStub::GetProp_CallDOMProxyWithGenerationNative; } else { - if (UpdateExistingGetPropCallStubs(stub, ICStub::GetProp_CallNativePrototype, - holder, obj, callee)) { - *attached = true; - return true; - } - - ICGetProp_CallNativePrototype::Compiler compiler(cx, monitorStub, obj, holder, callee, - script->pcToOffset(pc), outerClass); - newStub = compiler.getStub(compiler.getStubSpace(script)); + kind = ICStub::GetProp_CallDOMProxyNative; + } + Rooted proxy(cx, &obj->as()); + ICGetPropCallDOMProxyNativeCompiler compiler(cx, kind, monitorStub, proxy, holder, callee, + script->pcToOffset(pc)); + newStub = compiler.getStub(compiler.getStubSpace(script)); + } else if (obj == holder) { + if (UpdateExistingGetPropCallStubs(stub, ICStub::GetProp_CallNative, + obj, JS::NullPtr(), callee)) { + *attached = true; + return true; } - if (!newStub) - return false; - stub->addNewStub(newStub); - *attached = true; - return true; - } + ICGetProp_CallNative::Compiler compiler(cx, monitorStub, obj, callee, + script->pcToOffset(pc), outerClass); + newStub = compiler.getStub(compiler.getStubSpace(script)); + } else { + if (UpdateExistingGetPropCallStubs(stub, ICStub::GetProp_CallNativePrototype, + holder, obj, callee)) { + *attached = true; + return true; + } + + ICGetProp_CallNativePrototype::Compiler compiler(cx, monitorStub, obj, holder, callee, + script->pcToOffset(pc), outerClass); + newStub = compiler.getStub(compiler.getStubSpace(script)); + } + if (!newStub) + return false; + stub->addNewStub(newStub); + *attached = true; return true; } diff --git a/js/src/tests/ecma_5/misc/bug1126318.js b/js/src/tests/ecma_5/misc/bug1126318.js new file mode 100644 index 00000000000..73ccdc07a73 --- /dev/null +++ b/js/src/tests/ecma_5/misc/bug1126318.js @@ -0,0 +1,13 @@ +if (typeof window === "undefined") + window = this; + +Object.defineProperty(window, "foo", { + get: function() { return 5; }, + configurable: true +}); + +for (var i = 0; i < 100; ++i) + assertEq(window.foo, 5); + +if (typeof reportCompare === "function") + reportCompare(0, 0); From 540180c24cf0b27f4a9d224b3d2eb8252bfa5e78 Mon Sep 17 00:00:00 2001 From: Jan de Mooij Date: Fri, 30 Jan 2015 22:12:06 +0100 Subject: [PATCH 046/101] Bug 1066878 part 3 - Fix NewObjectWithGivenProto to actually use its allocKind argument. r=bhackett --- js/src/jsobjinlines.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/src/jsobjinlines.h b/js/src/jsobjinlines.h index 68af35c7f24..c3372a77acd 100644 --- a/js/src/jsobjinlines.h +++ b/js/src/jsobjinlines.h @@ -550,7 +550,8 @@ inline T * NewObjectWithGivenProto(ExclusiveContext *cx, JSObject *proto, JSObject *parent, gc::AllocKind allocKind, NewObjectKind newKind = GenericObject) { - JSObject *obj = NewObjectWithGivenProto(cx, &T::class_, TaggedProto(proto), parent, newKind); + JSObject *obj = NewObjectWithGivenProto(cx, &T::class_, TaggedProto(proto), parent, allocKind, + newKind); return obj ? &obj->as() : nullptr; } From aa4e523b480ac1872e383608b727861d933ac1d4 Mon Sep 17 00:00:00 2001 From: Brian O'Keefe Date: Tue, 20 Jan 2015 13:07:02 -0500 Subject: [PATCH 047/101] Bug 870366 - Part 1: Move PREF_JS_EXPORTS to moz.build (mozbuild logic). r=gps --- .../mozbuild/mozbuild/backend/recursivemake.py | 7 +++++++ python/mozbuild/mozbuild/frontend/context.py | 7 +++++++ python/mozbuild/mozbuild/frontend/data.py | 9 +++++++++ python/mozbuild/mozbuild/frontend/emitter.py | 4 ++++ .../backend/data/js_preference_files/moz.build | 12 ++++++++++++ .../test/backend/test_recursivemake.py | 18 ++++++++++++++++++ .../data/js_preference_files/moz.build | 11 +++++++++++ .../mozbuild/test/frontend/test_emitter.py | 16 ++++++++++++++++ 8 files changed, 84 insertions(+) create mode 100644 python/mozbuild/mozbuild/test/backend/data/js_preference_files/moz.build create mode 100644 python/mozbuild/mozbuild/test/frontend/data/js_preference_files/moz.build diff --git a/python/mozbuild/mozbuild/backend/recursivemake.py b/python/mozbuild/mozbuild/backend/recursivemake.py index ef6b4c84d92..de76b4221ae 100644 --- a/python/mozbuild/mozbuild/backend/recursivemake.py +++ b/python/mozbuild/mozbuild/backend/recursivemake.py @@ -46,6 +46,7 @@ from ..frontend.data import ( JARManifest, JavaJarData, JavaScriptModules, + JsPreferenceFile, Library, LocalInclude, PerSourceFlag, @@ -445,6 +446,12 @@ class RecursiveMakeBackend(CommonBackend): elif isinstance(obj, Resources): self._process_resources(obj, obj.resources, backend_file) + elif isinstance(obj, JsPreferenceFile): + if obj.path.startswith('/'): + backend_file.write('PREF_JS_EXPORTS += $(topsrcdir)%s\n' % obj.path) + else: + backend_file.write('PREF_JS_EXPORTS += $(srcdir)/%s\n' % obj.path) + elif isinstance(obj, JARManifest): backend_file.write('JAR_MANIFEST := %s\n' % obj.path) diff --git a/python/mozbuild/mozbuild/frontend/context.py b/python/mozbuild/mozbuild/frontend/context.py index 9b99dbba20e..9bea94a7f1a 100644 --- a/python/mozbuild/mozbuild/frontend/context.py +++ b/python/mozbuild/mozbuild/frontend/context.py @@ -648,6 +648,13 @@ VARIABLES = { populated by calling add_java_jar(). """, 'libs'), + 'JS_PREFERENCE_FILES': (StrictOrderingOnAppendList, list, + """Exported javascript files. + + A list of files copied into the dist directory for packaging and installation. + Path will be defined for gre or application prefs dir based on what is building. + """, 'libs'), + 'LIBRARY_DEFINES': (OrderedDict, dict, """Dictionary of compiler defines to declare for the entire library. diff --git a/python/mozbuild/mozbuild/frontend/data.py b/python/mozbuild/mozbuild/frontend/data.py index 3c88785c112..3a1e3c2a85c 100644 --- a/python/mozbuild/mozbuild/frontend/data.py +++ b/python/mozbuild/mozbuild/frontend/data.py @@ -244,6 +244,15 @@ class Resources(ContextDerived): defs.update(defines) self.defines = defs +class JsPreferenceFile(ContextDerived): + """Context derived container object for a Javascript preference file. + + Paths are assumed to be relative to the srcdir.""" + __slots__ = ('path') + + def __init__(self, context, path): + ContextDerived.__init__(self, context) + self.path = path class IPDLFile(ContextDerived): """Describes an individual .ipdl source file.""" diff --git a/python/mozbuild/mozbuild/frontend/emitter.py b/python/mozbuild/mozbuild/frontend/emitter.py index 6a7604c1027..ba27f3d6d37 100644 --- a/python/mozbuild/mozbuild/frontend/emitter.py +++ b/python/mozbuild/mozbuild/frontend/emitter.py @@ -47,6 +47,7 @@ from .data import ( IPDLFile, JARManifest, JavaScriptModules, + JsPreferenceFile, Library, Linkable, LinkageWrongKindError, @@ -555,6 +556,9 @@ class TreeMetadataEmitter(LoggingMixin): if resources: yield Resources(context, resources, defines) + for pref in sorted(context['JS_PREFERENCE_FILES']): + yield JsPreferenceFile(context, pref) + for kind, cls in [('PROGRAM', Program), ('HOST_PROGRAM', HostProgram)]: program = context.get(kind) if program: diff --git a/python/mozbuild/mozbuild/test/backend/data/js_preference_files/moz.build b/python/mozbuild/mozbuild/test/backend/data/js_preference_files/moz.build new file mode 100644 index 00000000000..9db18ebdc71 --- /dev/null +++ b/python/mozbuild/mozbuild/test/backend/data/js_preference_files/moz.build @@ -0,0 +1,12 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +JS_PREFERENCE_FILES = ['aa/aa.js', 'bb/bb.js'] +JS_PREFERENCE_FILES += ['cc/cc.js', 'dd/dd.js'] +JS_PREFERENCE_FILES += ['/ee/ee.js', '/ff/ff.js'] + +if CONFIG['_INVALID_CONFIG_VALUE']: + JS_PREFERENCE_FILES += ['invalid_val/prefs.js'] +else: + JS_PREFERENCE_FILES += ['valid_val/prefs.js'] diff --git a/python/mozbuild/mozbuild/test/backend/test_recursivemake.py b/python/mozbuild/mozbuild/test/backend/test_recursivemake.py index 8d045c85bfd..e1d1e8b7853 100644 --- a/python/mozbuild/mozbuild/test/backend/test_recursivemake.py +++ b/python/mozbuild/mozbuild/test/backend/test_recursivemake.py @@ -385,6 +385,24 @@ class TestRecursiveMakeBackend(BackendTester): self.assertIn('res/tests/test.manifest', m) self.assertIn('res/tests/extra.manifest', m) + def test_js_preference_files(self): + """Ensure PREF_JS_EXPORTS is written out correctly.""" + env = self._consume('js_preference_files', RecursiveMakeBackend) + + backend_path = os.path.join(env.topobjdir, 'backend.mk') + lines = [l.strip() for l in open(backend_path, 'rt').readlines()] + + # Avoid positional parameter and async related breakage + var = 'PREF_JS_EXPORTS' + found = [val for val in lines if val.startswith(var)] + + # Assignment[aa], append[cc], conditional[valid] + expected = ('aa/aa.js', 'bb/bb.js', 'cc/cc.js', 'dd/dd.js', 'valid_val/prefs.js') + expected_top = ('ee/ee.js', 'ff/ff.js') + self.assertEqual(found, + ['PREF_JS_EXPORTS += $(topsrcdir)/%s' % val for val in expected_top] + + ['PREF_JS_EXPORTS += $(srcdir)/%s' % val for val in expected]) + def test_test_manifests_files_written(self): """Ensure test manifests get turned into files.""" env = self._consume('test-manifests-written', RecursiveMakeBackend) diff --git a/python/mozbuild/mozbuild/test/frontend/data/js_preference_files/moz.build b/python/mozbuild/mozbuild/test/frontend/data/js_preference_files/moz.build new file mode 100644 index 00000000000..09e565ed1ba --- /dev/null +++ b/python/mozbuild/mozbuild/test/frontend/data/js_preference_files/moz.build @@ -0,0 +1,11 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +JS_PREFERENCE_FILES = ['ww/ww.js', 'xx/xx.js'] +JS_PREFERENCE_FILES += ['yy/yy.js'] + +if CONFIG['_INVALID_CONFIG_VALUE']: + JS_PREFERENCE_FILES += ['invalid_val/prefs.js'] +else: + JS_PREFERENCE_FILES += ['valid_val/prefs.js'] diff --git a/python/mozbuild/mozbuild/test/frontend/test_emitter.py b/python/mozbuild/mozbuild/test/frontend/test_emitter.py index 862d0f5171c..fddeea6556e 100644 --- a/python/mozbuild/mozbuild/test/frontend/test_emitter.py +++ b/python/mozbuild/mozbuild/test/frontend/test_emitter.py @@ -19,6 +19,7 @@ from mozbuild.frontend.data import ( HostSources, IPDLFile, JARManifest, + JsPreferenceFile, LocalInclude, Program, ReaderSummary, @@ -282,6 +283,21 @@ class TestEmitterBasic(unittest.TestCase): overwrite = resources._children['overwrite'] self.assertEqual(overwrite._strings, ['new.res']) + def test_preferences_js(self): + reader = self.reader('js_preference_files') + objs = self.read_topsrcdir(reader) + + prefs = [o.path for o in objs if isinstance(o, JsPreferenceFile)] + + prefsByDir = [ + 'valid_val/prefs.js', + 'ww/ww.js', + 'xx/xx.js', + 'yy/yy.js', + ] + + self.assertEqual(sorted(prefs), prefsByDir) + def test_program(self): reader = self.reader('program') objs = self.read_topsrcdir(reader) From 9035722dbf1232499e4b5f735e81841d2b309c38 Mon Sep 17 00:00:00 2001 From: Brian O'Keefe Date: Tue, 20 Jan 2015 13:07:03 -0500 Subject: [PATCH 048/101] Bug 870366 - Part 2: Move PREF_JS_EXPORTS to moz.build (easy moves). r=gps --- b2g/app/Makefile.in | 2 -- b2g/app/moz.build | 5 +++++ b2g/dev/app/Makefile.in | 5 ----- b2g/dev/app/moz.build | 5 +++++ browser/app/Makefile.in | 7 ------- browser/app/moz.build | 9 +++++++++ browser/branding/aurora/Makefile.in | 2 -- browser/branding/aurora/moz.build | 5 +++++ browser/branding/nightly/Makefile.in | 2 -- browser/branding/nightly/moz.build | 5 +++++ browser/branding/official/Makefile.in | 2 -- browser/branding/official/moz.build | 5 +++++ browser/branding/unofficial/Makefile.in | 2 -- browser/branding/unofficial/moz.build | 5 +++++ browser/devtools/webide/Makefile.in | 5 ----- browser/devtools/webide/moz.build | 5 +++++ browser/metro/profile/Makefile.in | 7 ------- browser/metro/profile/moz.build | 4 ++++ mobile/android/app/Makefile.in | 5 ----- mobile/android/app/moz.build | 5 +++++ services/common/Makefile.in | 5 ----- services/common/moz.build | 5 +++++ services/sync/Makefile.in | 1 - services/sync/moz.build | 5 +++++ webapprt/Makefile.in | 3 --- webapprt/moz.build | 5 +++++ xulrunner/app/Makefile.in | 1 - xulrunner/app/moz.build | 5 +++++ xulrunner/examples/simple/Makefile.in | 1 - xulrunner/examples/simple/moz.build | 7 ++++++- 30 files changed, 79 insertions(+), 51 deletions(-) delete mode 100644 b2g/dev/app/Makefile.in delete mode 100644 browser/devtools/webide/Makefile.in delete mode 100644 browser/metro/profile/Makefile.in delete mode 100644 mobile/android/app/Makefile.in delete mode 100644 services/common/Makefile.in diff --git a/b2g/app/Makefile.in b/b2g/app/Makefile.in index 46161c68c77..473c6a9392f 100644 --- a/b2g/app/Makefile.in +++ b/b2g/app/Makefile.in @@ -5,8 +5,6 @@ USE_RCS_MK := 1 include $(topsrcdir)/config/makefiles/rcs.mk -PREF_JS_EXPORTS = $(srcdir)/b2g.js - UA_UPDATE_FILE = ua-update.json $(UA_UPDATE_FILE): % : %.in diff --git a/b2g/app/moz.build b/b2g/app/moz.build index c6736b3df2d..0a9dc3fa181 100644 --- a/b2g/app/moz.build +++ b/b2g/app/moz.build @@ -76,3 +76,8 @@ if CONFIG['OS_ARCH'] == 'WINNT': ] FAIL_ON_WARNINGS = True + +JS_PREFERENCE_FILES += [ + 'b2g.js', +] + diff --git a/b2g/dev/app/Makefile.in b/b2g/dev/app/Makefile.in deleted file mode 100644 index 9451684c057..00000000000 --- a/b2g/dev/app/Makefile.in +++ /dev/null @@ -1,5 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -PREF_JS_EXPORTS += $(topsrcdir)/b2g/app/b2g.js diff --git a/b2g/dev/app/moz.build b/b2g/dev/app/moz.build index bdb19346930..dd98bcb16ab 100644 --- a/b2g/dev/app/moz.build +++ b/b2g/dev/app/moz.build @@ -4,3 +4,8 @@ DIST_SUBDIR = 'browser' export('DIST_SUBDIR') + +JS_PREFERENCE_FILES += [ + '/b2g/app/b2g.js', +] + diff --git a/browser/app/Makefile.in b/browser/app/Makefile.in index 83f0d8c4978..d1c6ebafb23 100644 --- a/browser/app/Makefile.in +++ b/browser/app/Makefile.in @@ -4,9 +4,6 @@ dist_dest = $(DIST)/$(MOZ_MACBUNDLE_NAME) -PREF_JS_EXPORTS = $(srcdir)/profile/firefox.js \ - $(NULL) - # hardcode en-US for the moment AB_CD = en-US @@ -18,10 +15,6 @@ DEFINES += \ -DPBMODE_ICO='"$(DIST)/branding/pbmode.ico"' \ $(NULL) -ifdef LIBXUL_SDK #{ -PREF_JS_EXPORTS += $(srcdir)/profile/channel-prefs.js -endif #} LIBXUL_SDK - # Build a binary bootstrapping with XRE_main ifndef MOZ_WINCONSOLE diff --git a/browser/app/moz.build b/browser/app/moz.build index 097bad1738d..626ccc0efc6 100644 --- a/browser/app/moz.build +++ b/browser/app/moz.build @@ -11,6 +11,15 @@ if CONFIG['OS_ARCH'] == 'WINNT' and (CONFIG['MOZ_METRO'] or CONFIG['MOZ_ASAN']): else: GeckoProgram(CONFIG['MOZ_APP_NAME'], msvcrt='static') +JS_PREFERENCE_FILES += [ + 'profile/firefox.js', +] + +if CONFIG['LIBXUL_SDK']: + PREF_JS_EXPORTS += [ + 'profile/channel-prefs.js', + ] + SOURCES += [ 'nsBrowserApp.cpp', ] diff --git a/browser/branding/aurora/Makefile.in b/browser/branding/aurora/Makefile.in index 573a6d19786..56ce50878f1 100644 --- a/browser/branding/aurora/Makefile.in +++ b/browser/branding/aurora/Makefile.in @@ -4,8 +4,6 @@ include $(topsrcdir)/config/config.mk -PREF_JS_EXPORTS = $(srcdir)/pref/firefox-branding.js - # On Windows only do this step for browser, skip for metro. ifeq ($(MOZ_WIDGET_TOOLKIT) $(DIST_SUBDIR),windows browser) BRANDING_FILES := \ diff --git a/browser/branding/aurora/moz.build b/browser/branding/aurora/moz.build index 3e2f88f1e36..ee56b1fecc1 100644 --- a/browser/branding/aurora/moz.build +++ b/browser/branding/aurora/moz.build @@ -8,3 +8,8 @@ DIRS += ['content', 'locales'] DIST_SUBDIR = 'browser' export('DIST_SUBDIR') + +JS_PREFERENCE_FILES += [ + 'pref/firefox-branding.js', +] + diff --git a/browser/branding/nightly/Makefile.in b/browser/branding/nightly/Makefile.in index 573a6d19786..56ce50878f1 100644 --- a/browser/branding/nightly/Makefile.in +++ b/browser/branding/nightly/Makefile.in @@ -4,8 +4,6 @@ include $(topsrcdir)/config/config.mk -PREF_JS_EXPORTS = $(srcdir)/pref/firefox-branding.js - # On Windows only do this step for browser, skip for metro. ifeq ($(MOZ_WIDGET_TOOLKIT) $(DIST_SUBDIR),windows browser) BRANDING_FILES := \ diff --git a/browser/branding/nightly/moz.build b/browser/branding/nightly/moz.build index 3e2f88f1e36..ee56b1fecc1 100644 --- a/browser/branding/nightly/moz.build +++ b/browser/branding/nightly/moz.build @@ -8,3 +8,8 @@ DIRS += ['content', 'locales'] DIST_SUBDIR = 'browser' export('DIST_SUBDIR') + +JS_PREFERENCE_FILES += [ + 'pref/firefox-branding.js', +] + diff --git a/browser/branding/official/Makefile.in b/browser/branding/official/Makefile.in index 573a6d19786..56ce50878f1 100644 --- a/browser/branding/official/Makefile.in +++ b/browser/branding/official/Makefile.in @@ -4,8 +4,6 @@ include $(topsrcdir)/config/config.mk -PREF_JS_EXPORTS = $(srcdir)/pref/firefox-branding.js - # On Windows only do this step for browser, skip for metro. ifeq ($(MOZ_WIDGET_TOOLKIT) $(DIST_SUBDIR),windows browser) BRANDING_FILES := \ diff --git a/browser/branding/official/moz.build b/browser/branding/official/moz.build index 3e2f88f1e36..ee56b1fecc1 100644 --- a/browser/branding/official/moz.build +++ b/browser/branding/official/moz.build @@ -8,3 +8,8 @@ DIRS += ['content', 'locales'] DIST_SUBDIR = 'browser' export('DIST_SUBDIR') + +JS_PREFERENCE_FILES += [ + 'pref/firefox-branding.js', +] + diff --git a/browser/branding/unofficial/Makefile.in b/browser/branding/unofficial/Makefile.in index 573a6d19786..56ce50878f1 100644 --- a/browser/branding/unofficial/Makefile.in +++ b/browser/branding/unofficial/Makefile.in @@ -4,8 +4,6 @@ include $(topsrcdir)/config/config.mk -PREF_JS_EXPORTS = $(srcdir)/pref/firefox-branding.js - # On Windows only do this step for browser, skip for metro. ifeq ($(MOZ_WIDGET_TOOLKIT) $(DIST_SUBDIR),windows browser) BRANDING_FILES := \ diff --git a/browser/branding/unofficial/moz.build b/browser/branding/unofficial/moz.build index 3e2f88f1e36..ee56b1fecc1 100644 --- a/browser/branding/unofficial/moz.build +++ b/browser/branding/unofficial/moz.build @@ -8,3 +8,8 @@ DIRS += ['content', 'locales'] DIST_SUBDIR = 'browser' export('DIST_SUBDIR') + +JS_PREFERENCE_FILES += [ + 'pref/firefox-branding.js', +] + diff --git a/browser/devtools/webide/Makefile.in b/browser/devtools/webide/Makefile.in deleted file mode 100644 index 099c204a932..00000000000 --- a/browser/devtools/webide/Makefile.in +++ /dev/null @@ -1,5 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -PREF_JS_EXPORTS = $(srcdir)/webide-prefs.js diff --git a/browser/devtools/webide/moz.build b/browser/devtools/webide/moz.build index eef3b2683d1..e9804d09ca6 100644 --- a/browser/devtools/webide/moz.build +++ b/browser/devtools/webide/moz.build @@ -23,3 +23,8 @@ EXTRA_JS_MODULES.devtools.webide += [ 'modules/tab-store.js', 'modules/utils.js' ] + +JS_PREFERENCE_FILES += [ + 'webide-prefs.js', +] + diff --git a/browser/metro/profile/Makefile.in b/browser/metro/profile/Makefile.in deleted file mode 100644 index 46da143fb4c..00000000000 --- a/browser/metro/profile/Makefile.in +++ /dev/null @@ -1,7 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this file, -# You can obtain one at http://mozilla.org/MPL/2.0/. - -include $(topsrcdir)/config/config.mk - -PREF_JS_EXPORTS = $(srcdir)/metro.js diff --git a/browser/metro/profile/moz.build b/browser/metro/profile/moz.build index 895d11993cf..78d3ef683fe 100644 --- a/browser/metro/profile/moz.build +++ b/browser/metro/profile/moz.build @@ -4,3 +4,7 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. +JS_PREFERENCE_FILES += [ + 'metro.js', +] + diff --git a/mobile/android/app/Makefile.in b/mobile/android/app/Makefile.in deleted file mode 100644 index a6d9c0626ec..00000000000 --- a/mobile/android/app/Makefile.in +++ /dev/null @@ -1,5 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -PREF_JS_EXPORTS = $(srcdir)/mobile.js diff --git a/mobile/android/app/moz.build b/mobile/android/app/moz.build index 1674e89af5e..2022be8e3be 100644 --- a/mobile/android/app/moz.build +++ b/mobile/android/app/moz.build @@ -12,3 +12,8 @@ for var in ('MOZ_UPDATER', 'MOZ_APP_UA_NAME', 'ANDROID_PACKAGE_NAME'): if CONFIG['MOZ_PKG_SPECIAL']: DEFINES['MOZ_PKG_SPECIAL'] = CONFIG['MOZ_PKG_SPECIAL'] + +JS_PREFERENCE_FILES += [ + 'mobile.js', +] + diff --git a/services/common/Makefile.in b/services/common/Makefile.in deleted file mode 100644 index 1e2b6c47e34..00000000000 --- a/services/common/Makefile.in +++ /dev/null @@ -1,5 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this file, -# You can obtain one at http://mozilla.org/MPL/2.0/. - -PREF_JS_EXPORTS := $(srcdir)/services-common.js diff --git a/services/common/moz.build b/services/common/moz.build index 3f0216e851e..199077618ee 100644 --- a/services/common/moz.build +++ b/services/common/moz.build @@ -32,3 +32,8 @@ TESTING_JS_MODULES.services.common += [ 'modules-testing/storageserver.js', 'modules-testing/utils.js', ] + +JS_PREFERENCE_FILES += [ + 'services-common.js', +] + diff --git a/services/sync/Makefile.in b/services/sync/Makefile.in index c21668e6ca2..e86ee160ffb 100644 --- a/services/sync/Makefile.in +++ b/services/sync/Makefile.in @@ -14,4 +14,3 @@ SYNC_PP_FLAGS := \ SYNC_PP_PATH = $(FINAL_TARGET)/modules/services-sync PP_TARGETS += SYNC_PP -PREF_JS_EXPORTS := $(srcdir)/services-sync.js diff --git a/services/sync/moz.build b/services/sync/moz.build index 3a825c2cce5..d0fee206db8 100644 --- a/services/sync/moz.build +++ b/services/sync/moz.build @@ -60,3 +60,8 @@ TESTING_JS_MODULES.services.sync += [ 'modules-testing/rotaryengine.js', 'modules-testing/utils.js', ] + +JS_PREFERENCE_FILES += [ + 'services-sync.js', +] + diff --git a/webapprt/Makefile.in b/webapprt/Makefile.in index c9e14535744..f34a64505b6 100644 --- a/webapprt/Makefile.in +++ b/webapprt/Makefile.in @@ -5,9 +5,6 @@ # Include config.mk explicitly so we can override FINAL_TARGET. include $(topsrcdir)/config/config.mk -PREF_JS_EXPORTS = $(srcdir)/prefs.js \ - $(NULL) - include $(topsrcdir)/config/rules.mk libs:: $(call mkdir_deps,$(FINAL_TARGET)) diff --git a/webapprt/moz.build b/webapprt/moz.build index 20373158aa7..5713f25d713 100644 --- a/webapprt/moz.build +++ b/webapprt/moz.build @@ -53,3 +53,8 @@ DEFINES['GRE_MILESTONE'] = CONFIG['GRE_MILESTONE'] DEFINES['MOZ_APP_BASENAME'] = CONFIG['MOZ_APP_BASENAME'] JAR_MANIFESTS += ['jar.mn'] + +JS_PREFERENCE_FILES += [ + 'prefs.js', +] + diff --git a/xulrunner/app/Makefile.in b/xulrunner/app/Makefile.in index 3e32f3959fe..8486ebb2d96 100644 --- a/xulrunner/app/Makefile.in +++ b/xulrunner/app/Makefile.in @@ -3,7 +3,6 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -PREF_JS_EXPORTS = $(srcdir)/xulrunner.js GARBAGE += $(addprefix $(DIST)/bin/defaults/pref/,xulrunner.js) ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT)) diff --git a/xulrunner/app/moz.build b/xulrunner/app/moz.build index 1335eeb4e0a..541738e2cb2 100644 --- a/xulrunner/app/moz.build +++ b/xulrunner/app/moz.build @@ -55,3 +55,8 @@ if CONFIG['OS_ARCH'] == 'WINNT': DISABLE_STL_WRAPPING = True FAIL_ON_WARNINGS = True + +JS_PREFERENCE_FILES += [ + 'xulrunner.js', +] + diff --git a/xulrunner/examples/simple/Makefile.in b/xulrunner/examples/simple/Makefile.in index 4706c3cbaa1..f31c0daee05 100644 --- a/xulrunner/examples/simple/Makefile.in +++ b/xulrunner/examples/simple/Makefile.in @@ -3,7 +3,6 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -PREF_JS_EXPORTS = $(srcdir)/simple-prefs.js DIST_FILES = application.ini ifneq (,$(filter windows,$(MOZ_WIDGET_TOOLKIT))) diff --git a/xulrunner/examples/simple/moz.build b/xulrunner/examples/simple/moz.build index ea9eecd70a3..444532379ce 100644 --- a/xulrunner/examples/simple/moz.build +++ b/xulrunner/examples/simple/moz.build @@ -8,4 +8,9 @@ DIRS += ['components'] XPI_NAME = 'simple' -JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file +JAR_MANIFESTS += ['jar.mn'] + +JS_PREFERENCE_FILES += [ + 'simple-prefs.js', +] + From 8e1f6e26f950702c55b25598fd7284e0d78a2954 Mon Sep 17 00:00:00 2001 From: Brian O'Keefe Date: Tue, 20 Jan 2015 13:07:03 -0500 Subject: [PATCH 049/101] Bug 870366 - Part 2b: Make l10n PREF_JS_EXPORTS use PP_TARGETS instead. r=glandium --- b2g/locales/Makefile.in | 5 ++++- browser/locales/Makefile.in | 5 ++++- browser/metro/locales/import/Makefile.in | 7 +++++-- mobile/android/locales/Makefile.in | 7 +++++-- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/b2g/locales/Makefile.in b/b2g/locales/Makefile.in index 14d68a9c32b..e2c01341c55 100644 --- a/b2g/locales/Makefile.in +++ b/b2g/locales/Makefile.in @@ -25,7 +25,10 @@ RETRIEVE_WINDOWS_INSTALLER = 1 MOZ_LANGPACK_EID=langpack-$(AB_CD)@b2g.mozilla.org -PREF_JS_EXPORTS = $(call MERGE_FILE,b2g-l10n.js) +L10N_PREF_JS_EXPORTS = $(call MERGE_FILE,b2g-l10n.js) +L10N_PREF_JS_EXPORTS_PATH = $(FINAL_TARGET)/$(PREF_DIR) +L10N_PREF_JS_EXPORTS_FLAGS = $(PREF_PPFLAGS) --silence-missing-directive-warnings +PP_TARGETS += L10N_PREF_JS_EXPORTS ifneq (,$(filter cocoa,$(MOZ_WIDGET_TOOLKIT))) MOZ_PKG_MAC_DSSTORE=$(_ABS_DIST)/branding/dsstore diff --git a/browser/locales/Makefile.in b/browser/locales/Makefile.in index c7cb1a39413..f6b43cce10d 100644 --- a/browser/locales/Makefile.in +++ b/browser/locales/Makefile.in @@ -44,7 +44,10 @@ RETRIEVE_WINDOWS_INSTALLER = 1 MOZ_LANGPACK_EID=langpack-$(AB_CD)@firefox.mozilla.org -PREF_JS_EXPORTS = $(call MERGE_FILE,firefox-l10n.js) +L10N_PREF_JS_EXPORTS = $(call MERGE_FILE,firefox-l10n.js) +L10N_PREF_JS_EXPORTS_PATH = $(FINAL_TARGET)/$(PREF_DIR) +L10N_PREF_JS_EXPORTS_FLAGS = $(PREF_PPFLAGS) --silence-missing-directive-warnings +PP_TARGETS += L10N_PREF_JS_EXPORTS ifneq (,$(filter cocoa,$(MOZ_WIDGET_TOOLKIT))) MOZ_PKG_MAC_DSSTORE=$(_ABS_DIST)/branding/dsstore diff --git a/browser/metro/locales/import/Makefile.in b/browser/metro/locales/import/Makefile.in index 0ca1f0a3d1e..e4c50bb5436 100644 --- a/browser/metro/locales/import/Makefile.in +++ b/browser/metro/locales/import/Makefile.in @@ -11,8 +11,11 @@ include $(topsrcdir)/config/config.mk # l10s prefs file # copying firefox-l10n.js over from LOCALE_SRCDIR or browser -PREF_JS_EXPORTS = $(firstword $(wildcard $(LOCALE_SRCDIR)/firefox-l10n.js) \ - $(topsrcdir)/$(relativesrcdir)/en-US/firefox-l10n.js ) +L10N_PREF_JS_EXPORTS = $(firstword $(wildcard $(LOCALE_SRCDIR)/firefox-l10n.js) \ + $(srcdir)/en-US/firefox-l10n.js ) +L10N_PREF_JS_EXPORTS_PATH = $(FINAL_TARGET)/$(PREF_DIR) +L10N_PREF_JS_EXPORTS_FLAGS = $(PREF_PPFLAGS) --silence-missing-directive-warnings +PP_TARGETS += L10N_PREF_JS_EXPORTS include $(topsrcdir)/config/rules.mk diff --git a/mobile/android/locales/Makefile.in b/mobile/android/locales/Makefile.in index be49741bead..bcd7c783d3d 100644 --- a/mobile/android/locales/Makefile.in +++ b/mobile/android/locales/Makefile.in @@ -10,8 +10,11 @@ SUBMAKEFILES += \ $(DEPTH)/mobile/locales/Makefile \ $(NULL) -PREF_JS_EXPORTS = $(firstword $(wildcard $(LOCALE_SRCDIR)/mobile-l10n.js) \ - @srcdir@/en-US/mobile-l10n.js ) +L10N_PREF_JS_EXPORTS = $(firstword $(wildcard $(LOCALE_SRCDIR)/mobile-l10n.js) \ + $(srcdir)/en-US/mobile-l10n.js ) +L10N_PREF_JS_EXPORTS_PATH = $(FINAL_TARGET)/$(PREF_DIR) +L10N_PREF_JS_EXPORTS_FLAGS = $(PREF_PPFLAGS) --silence-missing-directive-warnings +PP_TARGETS += L10N_PREF_JS_EXPORTS include $(topsrcdir)/config/rules.mk From 740ae5c0ac19311b4996ca8b62b64c1518da1673 Mon Sep 17 00:00:00 2001 From: Brian O'Keefe Date: Tue, 20 Jan 2015 13:07:04 -0500 Subject: [PATCH 050/101] Bug 870366 - Part 3: Prohibit PREF_JS_EXPORTS in Makefile.ins. r=gps --- CLOBBER | 2 +- config/baseconfig.mk | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CLOBBER b/CLOBBER index e0e95faab14..f04b62fe514 100644 --- a/CLOBBER +++ b/CLOBBER @@ -22,4 +22,4 @@ # changes to stick? As of bug 928195, this shouldn't be necessary! Please # don't change CLOBBER for WebIDL changes any more. -Bug 1109248 - This needed a CLOBBER on Windows and OSX. +Bug 870366 - Blacklisting PREF_JS_EXPORTS in Makefile.ins (because of 852814) diff --git a/config/baseconfig.mk b/config/baseconfig.mk index d4d8081c5b0..dd4af0d901e 100644 --- a/config/baseconfig.mk +++ b/config/baseconfig.mk @@ -90,6 +90,7 @@ _MOZBUILD_EXTERNAL_VARIABLES := \ NO_DIST_INSTALL \ OS_LIBS \ PARALLEL_DIRS \ + PREF_JS_EXPORTS \ PROGRAM \ PYTHON_UNIT_TESTS \ RESOURCE_FILES \ From 23dc1ca457da78dfa5f73e961de5bd9a12c643de Mon Sep 17 00:00:00 2001 From: Josh Grant Date: Fri, 30 Jan 2015 07:37:11 -0500 Subject: [PATCH 051/101] Bug 1105863 - Add docstring to timeouts. r=dburns --- testing/marionette/client/marionette/marionette.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/testing/marionette/client/marionette/marionette.py b/testing/marionette/client/marionette/marionette.py index d9b05732ccc..d15350a2b50 100644 --- a/testing/marionette/client/marionette/marionette.py +++ b/testing/marionette/client/marionette/marionette.py @@ -1135,6 +1135,19 @@ class Marionette(object): return response def timeouts(self, timeout_type, ms): + """An interface for managing timeout behaviour of a Marionette instance. + + Setting timeouts specifies the type and amount of time the Marionette instance should wait during requests. + + There are three types of timeouts that can be set: implicit, script and page load. + + * An implicit timeout specifies the amount of time a Marionette instance should wait when searching for elements. Here, marionette polls a page until an element is found or the timeout expires, whichever occurs first. When searching for multiple elements, the driver should poll the page until at least one element is found or the timeout expires, at which point it should return an empty list. + * A script timeout specifies the amount of time the Marionette instance should wait after calling executeAsyncScript for the callback to have executed before returning a timeout response. + * A page load timeout specifies the amount of time the Marionette instance should wait for a page load operation to complete. If this limit is exceeded, the Marionette instance will return a "timeout" response status. + + :param timeout_type: A string value specifying the timeout type. This must be one of three types: 'implicit', 'script' or 'page load' + :param ms: A number value specifying the timeout length in milliseconds (ms) + """ assert(timeout_type == self.TIMEOUT_SEARCH or timeout_type == self.TIMEOUT_SCRIPT or timeout_type == self.TIMEOUT_PAGE) response = self._send_message('timeouts', 'ok', type=timeout_type, ms=ms) return response From 451ea54d508a7cbad1c2cb72f40f1e0b47f7c0e2 Mon Sep 17 00:00:00 2001 From: Jim Blandy Date: Wed, 28 Jan 2015 16:52:15 -0800 Subject: [PATCH 052/101] Bug 1124752: Provide more information when js/src/jit-test/tests/debug/Memory-takeCensus-02.js fails. r=sfink --- .../tests/debug/Memory-takeCensus-02.js | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/js/src/jit-test/tests/debug/Memory-takeCensus-02.js b/js/src/jit-test/tests/debug/Memory-takeCensus-02.js index 10babe34b92..fbd529d2530 100644 --- a/js/src/jit-test/tests/debug/Memory-takeCensus-02.js +++ b/js/src/jit-test/tests/debug/Memory-takeCensus-02.js @@ -20,8 +20,24 @@ Census.walkCensus(census1, "census1", Census.assertAllNotLessThan(census0)); function pointCheck(label, lhs, rhs, objComp, funComp) { print(label); - assertEq(objComp(lhs.objects.Object.count, rhs.objects.Object.count), true); - assertEq(funComp(lhs.objects.Function.count, rhs.objects.Function.count), true); + try { + assertEq(objComp(lhs.objects.Object.count, rhs.objects.Object.count), true); + assertEq(funComp(lhs.objects.Function.count, rhs.objects.Function.count), true); + } catch (ex) { + print("pointCheck failed: " + ex); + print("lhs: " + JSON.stringify(lhs, undefined, 2)); + print("rhs: " + JSON.stringify(rhs, undefined, 2)); + + // Do it again, and put the result where Mozilla's continuous integration + // code will find it. + var upload_dir = os.getenv("MOZ_UPLOAD_DIR") || "."; + redirect(upload_dir + "/Memory-takeCensus-02.txt"); + print("pointCheck failed: " + ex); + print("lhs: " + JSON.stringify(lhs, undefined, 2)); + print("rhs: " + JSON.stringify(rhs, undefined, 2)); + + throw ex; + } } function eq(lhs, rhs) { return lhs === rhs; } From a2e10941319c264bfcce7f704dafa4a0782b9bb6 Mon Sep 17 00:00:00 2001 From: Geoff Brown Date: Fri, 30 Jan 2015 15:23:40 -0700 Subject: [PATCH 053/101] Bug 1127457 - Fix check for zip in devicemanagerADB.py; r=armenzg --- testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py b/testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py index 76f55ed3584..5cb6f2597e0 100644 --- a/testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py +++ b/testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py @@ -693,8 +693,13 @@ class DeviceManagerADB(DeviceManager): return False def _isLocalZipAvailable(self): + def _noOutput(line): + # suppress output from zip ProcessHandler + pass try: - self._checkCmd(["zip", "-?"]) + proc = ProcessHandler(["zip", "-?"], storeOutput=False, processOutputLine=_noOutput) + proc.run() + proc.wait() except: return False return True From 3ee5171ca3e478326b4f5ef30715ca8fe7f45337 Mon Sep 17 00:00:00 2001 From: Geoff Brown Date: Fri, 30 Jan 2015 15:23:41 -0700 Subject: [PATCH 054/101] Bug 1127928 - Increase pushDir timeout for xpcshell tests directory to 600 seconds; r=wlach --- testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py | 5 +++-- testing/xpcshell/remotexpcshelltests.py | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py b/testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py index 5cb6f2597e0..611baaba11e 100644 --- a/testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py +++ b/testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py @@ -249,13 +249,14 @@ class DeviceManagerADB(DeviceManager): self._logger.warning(traceback.format_exc()) self._logger.warning("zip/unzip failure: falling back to normal push") self._useZip = False - self.pushDir(localDir, remoteDir, retryLimit=retryLimit) + self.pushDir(localDir, remoteDir, retryLimit=retryLimit, timeout=timeout) else: tmpDir = tempfile.mkdtemp() # copytree's target dir must not already exist, so create a subdir tmpDirTarget = os.path.join(tmpDir, "tmp") shutil.copytree(localDir, tmpDirTarget) - self._checkCmd(["push", tmpDirTarget, remoteDir], retryLimit=retryLimit) + self._checkCmd(["push", tmpDirTarget, remoteDir], + retryLimit=retryLimit, timeout=timeout) mozfile.remove(tmpDir) def dirExists(self, remotePath): diff --git a/testing/xpcshell/remotexpcshelltests.py b/testing/xpcshell/remotexpcshelltests.py index 00fd56f6043..0a1f1d09bd5 100644 --- a/testing/xpcshell/remotexpcshelltests.py +++ b/testing/xpcshell/remotexpcshelltests.py @@ -455,7 +455,10 @@ class XPCShellRemote(xpcshell.XPCShellTests, object): def setupTestDir(self): print 'pushing %s' % self.xpcDir try: - self.device.pushDir(self.xpcDir, self.remoteScriptsDir, retryLimit=10) + # The tests directory can be quite large: 5000 files and growing! + # Sometimes - like on a low-end aws instance running an emulator - the push + # may exceed the default 5 minute timeout, so we increase it here to 10 minutes. + self.device.pushDir(self.xpcDir, self.remoteScriptsDir, timeout=600, retryLimit=10) except TypeError: # Foopies have an older mozdevice ver without retryLimit self.device.pushDir(self.xpcDir, self.remoteScriptsDir) From f99e876dd531bddb9e14ba15c9f44442194101f2 Mon Sep 17 00:00:00 2001 From: Gary Kwong Date: Fri, 30 Jan 2015 15:44:15 -0800 Subject: [PATCH 055/101] Bug 1128097 - Unable to compile js 32-bit ARM-simulator shells on Mac. rs=jandem --- js/src/jit/arm/Simulator-arm.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/src/jit/arm/Simulator-arm.cpp b/js/src/jit/arm/Simulator-arm.cpp index ed55b0824d5..2637dc3a870 100644 --- a/js/src/jit/arm/Simulator-arm.cpp +++ b/js/src/jit/arm/Simulator-arm.cpp @@ -365,7 +365,7 @@ Simulator::Create() if (!sim->init()) { js_delete(sim); - return false; + return nullptr; } if (getenv("ARM_SIM_ICACHE_CHECKS")) From dae4d2a0ea5cf547a28e596291ae34fb63844c56 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 30 Jan 2015 16:05:55 -0800 Subject: [PATCH 056/101] Bug 1125202 - SpiderMonkey: Delete unused functions r=jandem --- .../jit/shared/AssemblerBuffer-x86-shared.h | 28 +---- js/src/jit/shared/BaseAssembler-x86-shared.h | 116 ------------------ js/src/jit/x64/Assembler-x64.cpp | 2 +- 3 files changed, 2 insertions(+), 144 deletions(-) diff --git a/js/src/jit/shared/AssemblerBuffer-x86-shared.h b/js/src/jit/shared/AssemblerBuffer-x86-shared.h index 3b1b3402b8c..c7b88fd9bd6 100644 --- a/js/src/jit/shared/AssemblerBuffer-x86-shared.h +++ b/js/src/jit/shared/AssemblerBuffer-x86-shared.h @@ -172,32 +172,6 @@ namespace jit { return m_oom; } - /* - * The user must check for a NULL return value, which means - * no code was generated, or there was an OOM. - */ - void* executableAllocAndCopy(js::jit::ExecutableAllocator* allocator, - js::jit::ExecutablePool** poolp, js::jit::CodeKind kind) - { - if (m_oom || m_size == 0) { - *poolp = NULL; - return 0; - } - - m_allocSize = js::AlignBytes(m_size, sizeof(void *)); - - void* result = allocator->alloc(m_allocSize, poolp, kind); - if (!result) { - *poolp = NULL; - return 0; - } - MOZ_ASSERT(*poolp); - - js::jit::ExecutableAllocator::makeWritable(result, m_size); - - return memcpy(result, m_buffer, m_size); - } - unsigned char *buffer() const { MOZ_ASSERT(!m_oom); return reinterpret_cast(m_buffer); @@ -228,7 +202,7 @@ namespace jit { * can continue assembling into the buffer, deferring OOM checking * until the user wants to read code out of the buffer. * - * See also the |executableAllocAndCopy| and |buffer| methods. + * See also the |buffer| method. */ void grow(size_t extraCapacity = 0) diff --git a/js/src/jit/shared/BaseAssembler-x86-shared.h b/js/src/jit/shared/BaseAssembler-x86-shared.h index e58ed67fcbe..6b18c55641a 100644 --- a/js/src/jit/shared/BaseAssembler-x86-shared.h +++ b/js/src/jit/shared/BaseAssembler-x86-shared.h @@ -3889,124 +3889,12 @@ threeByteOpImmSimd("vblendps", VEX_PD, OP3_BLENDPS_VpsWpsIb, ESCAPE_BLENDPS, imm setRel32(code + from.m_offset, code + to.m_offset); } - static void linkJump(void* code, JmpSrc from, void* to) - { - MOZ_ASSERT(from.m_offset != -1); - - staticSpew("##link ((%d)) jumps to ((%p))", from.m_offset, to); - setRel32(reinterpret_cast(code) + from.m_offset, to); - } - - static void linkCall(void* code, JmpSrc from, void* to) - { - MOZ_ASSERT(from.m_offset != -1); - - staticSpew("##linkCall"); - setRel32(reinterpret_cast(code) + from.m_offset, to); - } - - static void linkPointer(void* code, JmpDst where, void* value) - { - MOZ_ASSERT(where.m_offset != -1); - - staticSpew("##linkPointer"); - setPointer(reinterpret_cast(code) + where.m_offset, value); - } - - static void relinkJump(void* from, void* to) - { - staticSpew("##relinkJump ((from=%p)) ((to=%p))", from, to); - setRel32(from, to); - } - static bool canRelinkJump(void* from, void* to) { intptr_t offset = reinterpret_cast(to) - reinterpret_cast(from); return (offset == static_cast(offset)); } - static void relinkCall(void* from, void* to) - { - staticSpew("##relinkCall ((from=%p)) ((to=%p))", from, to); - setRel32(from, to); - } - - static void repatchInt32(void* where, int32_t value) - { - staticSpew("##relinkInt32 ((where=%p)) ((value=%d))", where, value); - setInt32(where, value); - } - - static void repatchPointer(void* where, const void* value) - { - staticSpew("##repatchPtr ((where=%p)) ((value=%p))", where, value); - setPointer(where, value); - } - - static void repatchLoadPtrToLEA(void* where) - { - staticSpew("##repatchLoadPtrToLEA ((where=%p))", where); - -#ifdef JS_CODEGEN_X64 - // On x86-64 pointer memory accesses require a 64-bit operand, and as - // such a REX prefix. Skip over the prefix byte. - where = reinterpret_cast(where) + 1; -#endif - *reinterpret_cast(where) = static_cast(OP_LEA); - } - - static void repatchLEAToLoadPtr(void* where) - { - staticSpew("##repatchLEAToLoadPtr ((where=%p))", where); -#ifdef JS_CODEGEN_X64 - // On x86-64 pointer memory accesses require a 64-bit operand, and as - // such a REX prefix. Skip over the prefix byte. - where = reinterpret_cast(where) + 1; -#endif - *reinterpret_cast(where) = static_cast(OP_MOV_GvEv); - } - - static unsigned getCallReturnOffset(JmpSrc call) - { - MOZ_ASSERT(call.m_offset >= 0); - return call.m_offset; - } - - static void* getRelocatedAddress(void* code, JmpSrc jump) - { - MOZ_ASSERT(jump.m_offset != -1); - - return reinterpret_cast(reinterpret_cast(code) + jump.m_offset); - } - - static void* getRelocatedAddress(void* code, JmpDst destination) - { - MOZ_ASSERT(destination.m_offset != -1); - - return reinterpret_cast(reinterpret_cast(code) + destination.m_offset); - } - - static int getDifferenceBetweenLabels(JmpDst src, JmpDst dst) - { - return dst.m_offset - src.m_offset; - } - - static int getDifferenceBetweenLabels(JmpDst src, JmpSrc dst) - { - return dst.m_offset - src.m_offset; - } - - static int getDifferenceBetweenLabels(JmpSrc src, JmpDst dst) - { - return dst.m_offset - src.m_offset; - } - - void* executableAllocAndCopy(js::jit::ExecutableAllocator* allocator, - js::jit::ExecutablePool **poolp, js::jit::CodeKind kind) - { - return m_formatter.executableAllocAndCopy(allocator, poolp, kind); - } - void executableCopy(void* buffer) { memcpy(buffer, m_formatter.buffer(), size()); @@ -5374,10 +5262,6 @@ private: bool oom() const { return m_buffer.oom(); } bool isAligned(int alignment) const { return m_buffer.isAligned(alignment); } void* data() const { return m_buffer.data(); } - void* executableAllocAndCopy(js::jit::ExecutableAllocator* allocator, - js::jit::ExecutablePool** poolp, js::jit::CodeKind kind) { - return m_buffer.executableAllocAndCopy(allocator, poolp, kind); - } private: diff --git a/js/src/jit/x64/Assembler-x64.cpp b/js/src/jit/x64/Assembler-x64.cpp index a7937256810..9846f2b5b21 100644 --- a/js/src/jit/x64/Assembler-x64.cpp +++ b/js/src/jit/x64/Assembler-x64.cpp @@ -230,7 +230,7 @@ Assembler::executableCopy(uint8_t *buffer) // Now patch the pointer, note that we need to align it to // *after* the extended jump, i.e. after the 64-bit immedate. - X86Assembler::repatchPointer(entry + SizeOfExtendedJump, rp.target); + X86Assembler::setPointer(entry + SizeOfExtendedJump, rp.target); } } } From 83c3118897638658400511e997c75c8455f49563 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 30 Jan 2015 16:05:55 -0800 Subject: [PATCH 057/101] Bug 1125202 - SpiderMonkey: Add MOZ_WARN_UNUSED_RESULT to JmpSrc returns r=jandem --- js/src/jit/shared/BaseAssembler-x86-shared.h | 60 ++++++++++++-------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/js/src/jit/shared/BaseAssembler-x86-shared.h b/js/src/jit/shared/BaseAssembler-x86-shared.h index 6b18c55641a..6f96cf9b25f 100644 --- a/js/src/jit/shared/BaseAssembler-x86-shared.h +++ b/js/src/jit/shared/BaseAssembler-x86-shared.h @@ -2390,7 +2390,8 @@ public: m_formatter.oneByteOp64(OP_MOVSXD_GvEv, src, dst); } - JmpSrc movl_ripr(RegisterID dst) + MOZ_WARN_UNUSED_RESULT JmpSrc + movl_ripr(RegisterID dst) { m_formatter.oneByteRipOp(OP_MOV_GvEv, 0, (RegisterID)dst); JmpSrc label(m_formatter.size()); @@ -2398,7 +2399,8 @@ public: return label; } - JmpSrc movl_rrip(RegisterID src) + MOZ_WARN_UNUSED_RESULT JmpSrc + movl_rrip(RegisterID src) { m_formatter.oneByteRipOp(OP_MOV_EvGv, 0, (RegisterID)src); JmpSrc label(m_formatter.size()); @@ -2406,7 +2408,8 @@ public: return label; } - JmpSrc movq_ripr(RegisterID dst) + MOZ_WARN_UNUSED_RESULT JmpSrc + movq_ripr(RegisterID dst) { m_formatter.oneByteRipOp64(OP_MOV_GvEv, 0, dst); JmpSrc label(m_formatter.size()); @@ -2610,7 +2613,8 @@ public: m_formatter.oneByteOp64(OP_LEA, offset, base, dst); } - JmpSrc leaq_rip(RegisterID dst) + MOZ_WARN_UNUSED_RESULT JmpSrc + leaq_rip(RegisterID dst) { m_formatter.oneByteRipOp64(OP_LEA, 0, dst); JmpSrc label(m_formatter.size()); @@ -2621,7 +2625,8 @@ public: // Flow control: - JmpSrc call() + MOZ_WARN_UNUSED_RESULT JmpSrc + call() { m_formatter.oneByteOp(OP_CALL_rel32); JmpSrc r = m_formatter.immediateRel32(); @@ -2629,12 +2634,10 @@ public: return r; } - JmpSrc call(RegisterID dst) + void call(RegisterID dst) { m_formatter.oneByteOp(OP_GROUP5_Ev, dst, GROUP5_OP_CALLN); - JmpSrc r = JmpSrc(m_formatter.size()); spew("call *%s", nameIReg(dst)); - return r; } void call_m(int32_t offset, RegisterID base) @@ -2646,7 +2649,8 @@ public: // Comparison of EAX against a 32-bit immediate. The immediate is patched // in as if it were a jump target. The intention is to toggle the first // byte of the instruction between a CMP and a JMP to produce a pseudo-NOP. - JmpSrc cmp_eax() + MOZ_WARN_UNUSED_RESULT JmpSrc + cmp_eax() { m_formatter.oneByteOp(OP_CMP_EAXIv); JmpSrc r = m_formatter.immediateRel32(); @@ -2679,14 +2683,10 @@ public: return r; } - // Return a JmpSrc so we have a label to the jump, so we can use this - // To make a tail recursive call on x86-64. The MacroAssembler - // really shouldn't wrap this as a Jump, since it can't be linked. :-/ - JmpSrc jmp_r(RegisterID dst) + void jmp_r(RegisterID dst) { spew("jmp *%s", nameIReg(dst)); m_formatter.oneByteOp(OP_GROUP5_Ev, dst, GROUP5_OP_JMPN); - return JmpSrc(m_formatter.size()); } void jmp_m(int32_t offset, RegisterID base) @@ -3258,27 +3258,33 @@ public: twoByteOpSimd("vmovups", VEX_PS, OP2_MOVPS_WpsVps, address, X86Registers::invalid_xmm, src); } #ifdef JS_CODEGEN_X64 - JmpSrc vmovsd_ripr(XMMRegisterID dst) + MOZ_WARN_UNUSED_RESULT JmpSrc + vmovsd_ripr(XMMRegisterID dst) { return twoByteRipOpSimd("vmovsd", VEX_SD, OP2_MOVSD_VsdWsd, 0, X86Registers::invalid_xmm, dst); } - JmpSrc vmovss_ripr(XMMRegisterID dst) + MOZ_WARN_UNUSED_RESULT JmpSrc + vmovss_ripr(XMMRegisterID dst) { return twoByteRipOpSimd("vmovss", VEX_SS, OP2_MOVSD_VsdWsd, 0, X86Registers::invalid_xmm, dst); } - JmpSrc vmovsd_rrip(XMMRegisterID src) + MOZ_WARN_UNUSED_RESULT JmpSrc + vmovsd_rrip(XMMRegisterID src) { return twoByteRipOpSimd("vmovsd", VEX_SD, OP2_MOVSD_WsdVsd, 0, X86Registers::invalid_xmm, src); } - JmpSrc vmovss_rrip(XMMRegisterID src) + MOZ_WARN_UNUSED_RESULT JmpSrc + vmovss_rrip(XMMRegisterID src) { return twoByteRipOpSimd("vmovss", VEX_SS, OP2_MOVSD_WsdVsd, 0, X86Registers::invalid_xmm, src); } - JmpSrc vmovdqa_rrip(XMMRegisterID src) + MOZ_WARN_UNUSED_RESULT JmpSrc + vmovdqa_rrip(XMMRegisterID src) { return twoByteRipOpSimd("vmovdqa", VEX_PD, OP2_MOVDQ_WdqVdq, 0, X86Registers::invalid_xmm, src); } - JmpSrc vmovaps_rrip(XMMRegisterID src) + MOZ_WARN_UNUSED_RESULT JmpSrc + vmovaps_rrip(XMMRegisterID src) { return twoByteRipOpSimd("vmovdqa", VEX_PS, OP2_MOVAPS_WsdVsd, 0, X86Registers::invalid_xmm, src); } @@ -3356,12 +3362,14 @@ public: } #ifdef JS_CODEGEN_X64 - JmpSrc vmovaps_ripr(XMMRegisterID dst) + MOZ_WARN_UNUSED_RESULT JmpSrc + vmovaps_ripr(XMMRegisterID dst) { return twoByteRipOpSimd("vmovaps", VEX_PS, OP2_MOVAPS_VsdWsd, 0, X86Registers::invalid_xmm, dst); } - JmpSrc vmovdqa_ripr(XMMRegisterID dst) + MOZ_WARN_UNUSED_RESULT JmpSrc + vmovdqa_ripr(XMMRegisterID dst) { return twoByteRipOpSimd("vmovdqa", VEX_PD, OP2_MOVDQ_VdqWdq, 0, X86Registers::invalid_xmm, dst); } @@ -4009,8 +4017,9 @@ private: } #ifdef JS_CODEGEN_X64 - JmpSrc twoByteRipOpSimd(const char *name, VexOperandType ty, TwoByteOpcodeID opcode, - int ripOffset, XMMRegisterID src0, XMMRegisterID dst) + MOZ_WARN_UNUSED_RESULT JmpSrc + twoByteRipOpSimd(const char *name, VexOperandType ty, TwoByteOpcodeID opcode, + int ripOffset, XMMRegisterID src0, XMMRegisterID dst) { if (useLegacySSEEncoding(src0, dst)) { m_formatter.legacySSEPrefix(ty); @@ -5190,7 +5199,8 @@ private: m_buffer.putInt64Unchecked(imm); } - JmpSrc immediateRel32() + MOZ_WARN_UNUSED_RESULT JmpSrc + immediateRel32() { m_buffer.putIntUnchecked(0); return JmpSrc(m_buffer.size()); From 286f3354f9ce9cd66b0224177b0758b380142e65 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 30 Jan 2015 16:05:55 -0800 Subject: [PATCH 058/101] Bug 1125202 - SpiderMonkey: Rename a BaseAssembler function for consistency r=jandem --- js/src/jit/shared/Assembler-x86-shared.h | 4 ++-- js/src/jit/shared/BaseAssembler-x86-shared.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/js/src/jit/shared/Assembler-x86-shared.h b/js/src/jit/shared/Assembler-x86-shared.h index 56918fbd949..a851a21735c 100644 --- a/js/src/jit/shared/Assembler-x86-shared.h +++ b/js/src/jit/shared/Assembler-x86-shared.h @@ -952,12 +952,12 @@ class AssemblerX86Shared : public AssemblerShared } } void call(Register reg) { - masm.call(reg.code()); + masm.call_r(reg.code()); } void call(const Operand &op) { switch (op.kind()) { case Operand::REG: - masm.call(op.reg()); + masm.call_r(op.reg()); break; case Operand::MEM_REG_DISP: masm.call_m(op.disp(), op.base()); diff --git a/js/src/jit/shared/BaseAssembler-x86-shared.h b/js/src/jit/shared/BaseAssembler-x86-shared.h index 6f96cf9b25f..8c1fd6c1c20 100644 --- a/js/src/jit/shared/BaseAssembler-x86-shared.h +++ b/js/src/jit/shared/BaseAssembler-x86-shared.h @@ -2634,7 +2634,7 @@ public: return r; } - void call(RegisterID dst) + void call_r(RegisterID dst) { m_formatter.oneByteOp(OP_GROUP5_Ev, dst, GROUP5_OP_CALLN); spew("call *%s", nameIReg(dst)); From ea8f310230734071afe4adeb13b038c5e8a4c698 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 30 Jan 2015 16:05:55 -0800 Subject: [PATCH 059/101] Bug 1125202 - SpiderMonkey: Use BitwiseCast to do bitwise casts r=djvj --- js/src/jit/shared/BaseAssembler-x86-shared.h | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/js/src/jit/shared/BaseAssembler-x86-shared.h b/js/src/jit/shared/BaseAssembler-x86-shared.h index 8c1fd6c1c20..3b171b3aae9 100644 --- a/js/src/jit/shared/BaseAssembler-x86-shared.h +++ b/js/src/jit/shared/BaseAssembler-x86-shared.h @@ -5221,23 +5221,13 @@ private: void doubleConstant(double d) { m_buffer.ensureSpace(sizeof(double)); - union { - uint64_t u64; - double d; - } u; - u.d = d; - m_buffer.putInt64Unchecked(u.u64); + m_buffer.putInt64Unchecked(mozilla::BitwiseCast(d)); } void floatConstant(float f) { m_buffer.ensureSpace(sizeof(float)); - union { - uint32_t u32; - float f; - } u; - u.f = f; - m_buffer.putIntUnchecked(u.u32); + m_buffer.putIntUnchecked(mozilla::BitwiseCast(f)); } void int32x4Constant(const int32_t s[4]) From 9aef6c29f080e88109434c775d90416fe8d5700e Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 30 Jan 2015 16:05:55 -0800 Subject: [PATCH 060/101] Bug 1125202 - SpiderMonkey: Tidy the register name functions r=jandem --- .../jit/shared/AssemblerBuffer-x86-shared.h | 6 +- js/src/jit/shared/BaseAssembler-x86-shared.h | 683 +++++++++--------- 2 files changed, 362 insertions(+), 327 deletions(-) diff --git a/js/src/jit/shared/AssemblerBuffer-x86-shared.h b/js/src/jit/shared/AssemblerBuffer-x86-shared.h index c7b88fd9bd6..dce187c9c02 100644 --- a/js/src/jit/shared/AssemblerBuffer-x86-shared.h +++ b/js/src/jit/shared/AssemblerBuffer-x86-shared.h @@ -56,9 +56,9 @@ #define MEM_o32bs MEM_o32 "(%s,%s,%d)" #define ADDR_o(offset) PRETTYHEX(offset) -#define ADDR_os(offset, index, scale) ADDR_o(offset), nameIReg((index)), (1<<(scale)) -#define ADDR_ob(offset, base) ADDR_o(offset), nameIReg((base)) -#define ADDR_obs(offset, base, index, scale) ADDR_ob(offset, base), nameIReg((index)), (1<<(scale)) +#define ADDR_os(offset, index, scale) ADDR_o(offset), GPRegName((index)), (1<<(scale)) +#define ADDR_ob(offset, base) ADDR_o(offset), GPRegName((base)) +#define ADDR_obs(offset, base, index, scale) ADDR_ob(offset, base), GPRegName((index)), (1<<(scale)) #define ADDR_o32(offset) ADDR_o(offset) #define ADDR_o32s(offset, index, scale) ADDR_os(offset, index, scale) diff --git a/js/src/jit/shared/BaseAssembler-x86-shared.h b/js/src/jit/shared/BaseAssembler-x86-shared.h index 3b171b3aae9..957b7dc36a1 100644 --- a/js/src/jit/shared/BaseAssembler-x86-shared.h +++ b/js/src/jit/shared/BaseAssembler-x86-shared.h @@ -94,73 +94,107 @@ namespace X86Registers { ,invalid_xmm }; - static const char* nameFPReg(XMMRegisterID fpreg) + static const char *XMMRegName(XMMRegisterID fpreg) { - static const char* const xmmnames[16] - = { "%xmm0", "%xmm1", "%xmm2", "%xmm3", - "%xmm4", "%xmm5", "%xmm6", "%xmm7", - "%xmm8", "%xmm9", "%xmm10", "%xmm11", - "%xmm12", "%xmm13", "%xmm14", "%xmm15" }; - int off = (XMMRegisterID)fpreg - (XMMRegisterID)xmm0; - MOZ_ASSERT(off >= 0 && size_t(off) < mozilla::ArrayLength(xmmnames)); - return xmmnames[off]; + static const char *const names[] = { + "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7" +#ifdef JS_CODEGEN_X64 + , "%xmm8", "%xmm9", "%xmm10", "%xmm11", "%xmm12", "%xmm13", "%xmm14", "%xmm15" +#endif + }; + int off = fpreg - xmm0; + MOZ_ASSERT(off >= 0 && size_t(off) < mozilla::ArrayLength(names)); + return names[off]; } - static const char* nameIReg(int szB, RegisterID reg) +#ifdef JS_CODEGEN_X64 + static const char *GPReg64Name(RegisterID reg) { - static const char* const r64names[16] - = { "%rax", "%rcx", "%rdx", "%rbx", "%rsp", "%rbp", "%rsi", "%rdi", - "%r8", "%r9", "%r10", "%r11", "%r12", "%r13", "%r14", "%r15" }; - static const char* const r32names[16] - = { "%eax", "%ecx", "%edx", "%ebx", "%esp", "%ebp", "%esi", "%edi", - "%r8d", "%r9d", "%r10d", "%r11d", "%r12d", "%r13d", "%r14d", "%r15d" }; - static const char* const r16names[16] - = { "%ax", "%cx", "%dx", "%bx", "%sp", "%bp", "%si", "%di", - "%r8w", "%r9w", "%r10w", "%r11w", "%r12w", "%r13w", "%r14w", "%r15w" }; - static const char* const r8names[16] - = { "%al", "%cl", "%dl", "%bl", "%spl", "%bpl", "%sil", "%dil", - "%r8b", "%r9b", "%r10b", "%r11b", "%r12b", "%r13b", "%r14b", "%r15b" }; - int off = (RegisterID)reg - (RegisterID)eax; - MOZ_ASSERT(off >= 0 && size_t(off) < mozilla::ArrayLength(r64names)); - const char* const* tab = r64names; - switch (szB) { - case 1: tab = r8names; break; - case 2: tab = r16names; break; - case 4: tab = r32names; break; - } - return tab[off]; + static const char *const names[] = { + "%rax", "%rcx", "%rdx", "%rbx", "%rsp", "%rbp", "%rsi", "%rdi", + "%r8", "%r9", "%r10", "%r11", "%r12", "%r13", "%r14", "%r15" + }; + int off = reg - eax; + MOZ_ASSERT(off >= 0 && size_t(off) < mozilla::ArrayLength(names)); + return names[off]; + } +#endif + + static const char *GPReg32Name(RegisterID reg) + { + static const char *const names[] = { + "%eax", "%ecx", "%edx", "%ebx", "%esp", "%ebp", "%esi", "%edi" +#ifdef JS_CODEGEN_X64 + , "%r8d", "%r9d", "%r10d", "%r11d", "%r12d", "%r13d", "%r14d", "%r15d" +#endif + }; + int off = reg - eax; + MOZ_ASSERT(off >= 0 && size_t(off) < mozilla::ArrayLength(names)); + return names[off]; } - static const char* nameI8Reg_norex(RegisterID reg) + static const char *GPReg16Name(RegisterID reg) { - // The same as r8names above, except that %spl through %dil are replaced + static const char *const names[] = { + "%ax", "%cx", "%dx", "%bx", "%sp", "%bp", "%si", "%di" +#ifdef JS_CODEGEN_X64 + , "%r8w", "%r9w", "%r10w", "%r11w", "%r12w", "%r13w", "%r14w", "%r15w" +#endif + }; + int off = reg - eax; + MOZ_ASSERT(off >= 0 && size_t(off) < mozilla::ArrayLength(names)); + return names[off]; + } + + static const char *GPReg8Name(RegisterID reg) + { + // For %spl through %dil, assume we'll have a REX prefix. + static const char *const names[] = { + "%al", "%cl", "%dl", "%bl" +#ifdef JS_CODEGEN_X64 + , "%spl", "%bpl", "%sil", "%dil", + "%r8b", "%r9b", "%r10b", "%r11b", "%r12b", "%r13b", "%r14b", "%r15b" +#endif + }; + int off = reg - eax; + MOZ_ASSERT(off >= 0 && size_t(off) < mozilla::ArrayLength(names)); + return names[off]; + } + + // Like GPReg8Name, but emits non-REX names, when appropriate + static const char *GPReg8Name_norex(RegisterID reg) + { + // The same as names above, except that %spl through %dil are replaced // by %ah through %bh since there is to be no REX prefix. - static const char* const r8names_norex[16] - = { "%al", "%cl", "%dl", "%bl", "%ah", "%ch", "%dh", "%bh", - "%r8b", "%r9b", "%r10b", "%r11b", "%r12b", "%r13b", "%r14b", "%r15b" }; - int off = (RegisterID)reg - (RegisterID)eax; - MOZ_ASSERT(off >= 0 && size_t(off) < mozilla::ArrayLength(r8names_norex)); - return r8names_norex[off]; + static const char *const names[] = { + "%al", "%cl", "%dl", "%bl", "%ah", "%ch", "%dh", "%bh" +#ifdef JS_CODEGEN_X64 + , "%r8b", "%r9b", "%r10b", "%r11b", "%r12b", "%r13b", "%r14b", "%r15b" +#endif + }; + int off = reg - eax; + MOZ_ASSERT(off >= 0 && size_t(off) < mozilla::ArrayLength(names)); + return names[off]; } - static const char* nameIReg(RegisterID reg) + static const char *GPRegName(RegisterID reg) { -# ifdef JS_CODEGEN_X64 - return nameIReg(8, reg); -# else - return nameIReg(4, reg); -# endif +#ifdef JS_CODEGEN_X64 + return GPReg64Name(reg); +#else + return GPReg32Name(reg); +#endif } inline bool hasSubregL(RegisterID reg) { -# ifdef JS_CODEGEN_X64 +#ifdef JS_CODEGEN_X64 // In 64-bit mode, all registers have an 8-bit lo subreg. return true; -# else +#else // In 32-bit mode, only the first four registers do. return reg <= ebx; -# endif +#endif } inline bool hasSubregH(RegisterID reg) @@ -172,21 +206,20 @@ namespace X86Registers { return reg <= ebx; } - inline RegisterID getSubregH(RegisterID reg) { + inline RegisterID getSubregH(RegisterID reg) + { MOZ_ASSERT(hasSubregH(reg)); return RegisterID(reg + 4); } + // Byte operand register spl & above require a REX prefix (to prevent + // the 'H' registers be accessed). + inline bool ByteRegRequiresRex(RegisterID reg) + { + return reg >= esp; + } } /* namespace X86Registers */ -// Byte operand register spl & above require a REX prefix (to prevent -// the 'H' registers be accessed). -static inline bool -ByteRegRequiresRex(int reg) -{ - return (reg >= X86Registers::esp); -} - class X86Assembler : public GenericAssembler { public: typedef X86Registers::RegisterID RegisterID; @@ -615,13 +648,13 @@ public: void push_r(RegisterID reg) { - spew("push %s", nameIReg(reg)); + spew("push %s", GPRegName(reg)); m_formatter.oneByteOp(OP_PUSH_EAX, reg); } void pop_r(RegisterID reg) { - spew("pop %s", nameIReg(reg)); + spew("pop %s", GPRegName(reg)); m_formatter.oneByteOp(OP_POP_EAX, reg); } @@ -686,25 +719,25 @@ public: void addl_rr(RegisterID src, RegisterID dst) { - spew("addl %s, %s", nameIReg(4,src), nameIReg(4,dst)); + spew("addl %s, %s", GPReg32Name(src), GPReg32Name(dst)); m_formatter.oneByteOp(OP_ADD_GvEv, src, dst); } void addl_mr(int32_t offset, RegisterID base, RegisterID dst) { - spew("addl " MEM_ob ", %s", ADDR_ob(offset, base), nameIReg(4,dst)); + spew("addl " MEM_ob ", %s", ADDR_ob(offset, base), GPReg32Name(dst)); m_formatter.oneByteOp(OP_ADD_GvEv, offset, base, dst); } void addl_rm(RegisterID src, int32_t offset, RegisterID base) { - spew("addl %s, " MEM_ob, nameIReg(4,src), ADDR_ob(offset, base)); + spew("addl %s, " MEM_ob, GPReg32Name(src), ADDR_ob(offset, base)); m_formatter.oneByteOp(OP_ADD_EvGv, offset, base, src); } void addl_ir(int32_t imm, RegisterID dst) { - spew("addl $%d, %s", imm, nameIReg(4,dst)); + spew("addl $%d, %s", imm, GPReg32Name(dst)); if (CAN_SIGN_EXTEND_8_32(imm)) { m_formatter.oneByteOp(OP_GROUP1_EvIb, dst, GROUP1_OP_ADD); m_formatter.immediate8s(imm); @@ -719,7 +752,7 @@ public: void addl_i32r(int32_t imm, RegisterID dst) { // 32-bit immediate always, for patching. - spew("addl $0x%04x, %s", imm, nameIReg(4,dst)); + spew("addl $0x%04x, %s", imm, GPReg32Name(dst)); if (dst == X86Registers::eax) m_formatter.oneByteOp(OP_ADD_EAXIv); else @@ -742,25 +775,25 @@ public: #ifdef JS_CODEGEN_X64 void addq_rr(RegisterID src, RegisterID dst) { - spew("addq %s, %s", nameIReg(8,src), nameIReg(8,dst)); + spew("addq %s, %s", GPReg64Name(src), GPReg64Name(dst)); m_formatter.oneByteOp64(OP_ADD_GvEv, src, dst); } void addq_mr(int32_t offset, RegisterID base, RegisterID dst) { - spew("addq " MEM_ob ", %s", ADDR_ob(offset, base), nameIReg(8,dst)); + spew("addq " MEM_ob ", %s", ADDR_ob(offset, base), GPReg64Name(dst)); m_formatter.oneByteOp64(OP_ADD_GvEv, offset, base, dst); } void addq_mr(const void* addr, RegisterID dst) { - spew("addq %p, %s", addr, nameIReg(8, dst)); + spew("addq %p, %s", addr, GPReg64Name(dst)); m_formatter.oneByteOp64(OP_ADD_GvEv, addr, dst); } void addq_ir(int32_t imm, RegisterID dst) { - spew("addq $%d, %s", imm, nameIReg(8,dst)); + spew("addq $%d, %s", imm, GPReg64Name(dst)); if (CAN_SIGN_EXTEND_8_32(imm)) { m_formatter.oneByteOp64(OP_GROUP1_EvIb, dst, GROUP1_OP_ADD); m_formatter.immediate8s(imm); @@ -811,28 +844,28 @@ public: void lock_xaddb_rm(RegisterID srcdest, int32_t offset, RegisterID base) { - spew("lock xaddl %s, " MEM_ob, nameIReg(1, srcdest), ADDR_ob(offset, base)); + spew("lock xaddl %s, " MEM_ob, GPReg8Name(srcdest), ADDR_ob(offset, base)); m_formatter.oneByteOp(PRE_LOCK); m_formatter.twoByteOp(OP2_XADD_EbGb, offset, base, srcdest); } void lock_xaddb_rm(RegisterID srcdest, int32_t offset, RegisterID base, RegisterID index, int scale) { - spew("lock xaddl %s, " MEM_obs, nameIReg(1, srcdest), ADDR_obs(offset, base, index, scale)); + spew("lock xaddl %s, " MEM_obs, GPReg8Name(srcdest), ADDR_obs(offset, base, index, scale)); m_formatter.oneByteOp(PRE_LOCK); m_formatter.twoByteOp(OP2_XADD_EbGb, offset, base, index, scale, srcdest); } void lock_xaddl_rm(RegisterID srcdest, int32_t offset, RegisterID base) { - spew("lock xaddl %s, " MEM_ob, nameIReg(4,srcdest), ADDR_ob(offset, base)); + spew("lock xaddl %s, " MEM_ob, GPReg32Name(srcdest), ADDR_ob(offset, base)); m_formatter.oneByteOp(PRE_LOCK); m_formatter.twoByteOp(OP2_XADD_EvGv, offset, base, srcdest); } void lock_xaddl_rm(RegisterID srcdest, int32_t offset, RegisterID base, RegisterID index, int scale) { - spew("lock xaddl %s, " MEM_obs, nameIReg(4, srcdest), ADDR_obs(offset, base, index, scale)); + spew("lock xaddl %s, " MEM_obs, GPReg32Name(srcdest), ADDR_obs(offset, base, index, scale)); m_formatter.oneByteOp(PRE_LOCK); m_formatter.twoByteOp(OP2_XADD_EvGv, offset, base, index, scale, srcdest); } @@ -965,25 +998,25 @@ public: void andl_rr(RegisterID src, RegisterID dst) { - spew("andl %s, %s", nameIReg(4,src), nameIReg(4,dst)); + spew("andl %s, %s", GPReg32Name(src), GPReg32Name(dst)); m_formatter.oneByteOp(OP_AND_GvEv, src, dst); } void andl_mr(int32_t offset, RegisterID base, RegisterID dst) { - spew("andl " MEM_ob ", %s", ADDR_ob(offset, base), nameIReg(4,dst)); + spew("andl " MEM_ob ", %s", ADDR_ob(offset, base), GPReg32Name(dst)); m_formatter.oneByteOp(OP_AND_GvEv, offset, base, dst); } void andl_rm(RegisterID src, int32_t offset, RegisterID base) { - spew("andl %s, " MEM_ob, nameIReg(4,src), ADDR_ob(offset, base)); + spew("andl %s, " MEM_ob, GPReg32Name(src), ADDR_ob(offset, base)); m_formatter.oneByteOp(OP_AND_EvGv, offset, base, src); } void andl_ir(int32_t imm, RegisterID dst) { - spew("andl $0x%x, %s", imm, nameIReg(4,dst)); + spew("andl $0x%x, %s", imm, GPReg32Name(dst)); if (CAN_SIGN_EXTEND_8_32(imm)) { m_formatter.oneByteOp(OP_GROUP1_EvIb, dst, GROUP1_OP_AND); m_formatter.immediate8s(imm); @@ -1011,43 +1044,43 @@ public: #ifdef JS_CODEGEN_X64 void andq_rr(RegisterID src, RegisterID dst) { - spew("andq %s, %s", nameIReg(8,src), nameIReg(8,dst)); + spew("andq %s, %s", GPReg64Name(src), GPReg64Name(dst)); m_formatter.oneByteOp64(OP_AND_GvEv, src, dst); } void andq_mr(int32_t offset, RegisterID base, RegisterID dst) { - spew("andq " MEM_ob ", %s", ADDR_ob(offset, base), nameIReg(8,dst)); + spew("andq " MEM_ob ", %s", ADDR_ob(offset, base), GPReg64Name(dst)); m_formatter.oneByteOp64(OP_AND_GvEv, offset, base, dst); } void andq_mr(int32_t offset, RegisterID base, RegisterID index, int scale, RegisterID dst) { - spew("andq " MEM_obs ", %s", ADDR_obs(offset, base, index, scale), nameIReg(8,dst)); + spew("andq " MEM_obs ", %s", ADDR_obs(offset, base, index, scale), GPReg64Name(dst)); m_formatter.oneByteOp64(OP_AND_GvEv, offset, base, index, scale, dst); } void andq_mr(const void *addr, RegisterID dst) { - spew("andq %p, %s", addr, nameIReg(8,dst)); + spew("andq %p, %s", addr, GPReg64Name(dst)); m_formatter.oneByteOp64(OP_AND_GvEv, addr, dst); } void orq_mr(int32_t offset, RegisterID base, RegisterID dst) { - spew("orq " MEM_ob ", %s", ADDR_ob(offset, base), nameIReg(8,dst)); + spew("orq " MEM_ob ", %s", ADDR_ob(offset, base), GPReg64Name(dst)); m_formatter.oneByteOp64(OP_OR_GvEv, offset, base, dst); } void orq_mr(const void* addr, RegisterID dst) { - spew("orq %p, %s", addr, nameIReg(8, dst)); + spew("orq %p, %s", addr, GPReg64Name(dst)); m_formatter.oneByteOp64(OP_OR_GvEv, addr, dst); } void andq_ir(int32_t imm, RegisterID dst) { - spew("andq $0x%" PRIx64 ", %s", int64_t(imm), nameIReg(8,dst)); + spew("andq $0x%" PRIx64 ", %s", int64_t(imm), GPReg64Name(dst)); if (CAN_SIGN_EXTEND_8_32(imm)) { m_formatter.oneByteOp64(OP_GROUP1_EvIb, dst, GROUP1_OP_AND); m_formatter.immediate8s(imm); @@ -1101,7 +1134,7 @@ public: void negl_r(RegisterID dst) { - spew("negl %s", nameIReg(4,dst)); + spew("negl %s", GPReg32Name(dst)); m_formatter.oneByteOp(OP_GROUP3_Ev, dst, GROUP3_OP_NEG); } @@ -1113,7 +1146,7 @@ public: void notl_r(RegisterID dst) { - spew("notl %s", nameIReg(4,dst)); + spew("notl %s", GPReg32Name(dst)); m_formatter.oneByteOp(OP_GROUP3_Ev, dst, GROUP3_OP_NOT); } @@ -1125,25 +1158,25 @@ public: void orl_rr(RegisterID src, RegisterID dst) { - spew("orl %s, %s", nameIReg(4,src), nameIReg(4,dst)); + spew("orl %s, %s", GPReg32Name(src), GPReg32Name(dst)); m_formatter.oneByteOp(OP_OR_GvEv, src, dst); } void orl_mr(int32_t offset, RegisterID base, RegisterID dst) { - spew("orl " MEM_ob ", %s", ADDR_ob(offset, base), nameIReg(4,dst)); + spew("orl " MEM_ob ", %s", ADDR_ob(offset, base), GPReg32Name(dst)); m_formatter.oneByteOp(OP_OR_GvEv, offset, base, dst); } void orl_rm(RegisterID src, int32_t offset, RegisterID base) { - spew("orl %s, " MEM_ob, nameIReg(4,src), ADDR_ob(offset, base)); + spew("orl %s, " MEM_ob, GPReg32Name(src), ADDR_ob(offset, base)); m_formatter.oneByteOp(OP_OR_EvGv, offset, base, src); } void orl_ir(int32_t imm, RegisterID dst) { - spew("orl $0x%x, %s", imm, nameIReg(4,dst)); + spew("orl $0x%x, %s", imm, GPReg32Name(dst)); if (CAN_SIGN_EXTEND_8_32(imm)) { m_formatter.oneByteOp(OP_GROUP1_EvIb, dst, GROUP1_OP_OR); m_formatter.immediate8s(imm); @@ -1171,19 +1204,19 @@ public: #ifdef JS_CODEGEN_X64 void negq_r(RegisterID dst) { - spew("negq %s", nameIReg(8,dst)); + spew("negq %s", GPReg64Name(dst)); m_formatter.oneByteOp64(OP_GROUP3_Ev, dst, GROUP3_OP_NEG); } void orq_rr(RegisterID src, RegisterID dst) { - spew("orq %s, %s", nameIReg(8,src), nameIReg(8,dst)); + spew("orq %s, %s", GPReg64Name(src), GPReg64Name(dst)); m_formatter.oneByteOp64(OP_OR_GvEv, src, dst); } void orq_ir(int32_t imm, RegisterID dst) { - spew("orq $0x%" PRIx64 ", %s", int64_t(imm), nameIReg(8,dst)); + spew("orq $0x%" PRIx64 ", %s", int64_t(imm), GPReg64Name(dst)); if (CAN_SIGN_EXTEND_8_32(imm)) { m_formatter.oneByteOp64(OP_GROUP1_EvIb, dst, GROUP1_OP_OR); m_formatter.immediate8s(imm); @@ -1198,7 +1231,7 @@ public: void notq_r(RegisterID dst) { - spew("notq %s", nameIReg(8,dst)); + spew("notq %s", GPReg64Name(dst)); m_formatter.oneByteOp64(OP_GROUP3_Ev, dst, GROUP3_OP_NOT); } #else @@ -1217,25 +1250,25 @@ public: void subl_rr(RegisterID src, RegisterID dst) { - spew("subl %s, %s", nameIReg(4,src), nameIReg(4,dst)); + spew("subl %s, %s", GPReg32Name(src), GPReg32Name(dst)); m_formatter.oneByteOp(OP_SUB_GvEv, src, dst); } void subl_mr(int32_t offset, RegisterID base, RegisterID dst) { - spew("subl " MEM_ob ", %s", ADDR_ob(offset, base), nameIReg(4,dst)); + spew("subl " MEM_ob ", %s", ADDR_ob(offset, base), GPReg32Name(dst)); m_formatter.oneByteOp(OP_SUB_GvEv, offset, base, dst); } void subl_rm(RegisterID src, int32_t offset, RegisterID base) { - spew("subl %s, " MEM_ob, nameIReg(4,src), ADDR_ob(offset, base)); + spew("subl %s, " MEM_ob, GPReg32Name(src), ADDR_ob(offset, base)); m_formatter.oneByteOp(OP_SUB_EvGv, offset, base, src); } void subl_ir(int32_t imm, RegisterID dst) { - spew("subl $%d, %s", imm, nameIReg(4, dst)); + spew("subl $%d, %s", imm, GPReg32Name(dst)); if (CAN_SIGN_EXTEND_8_32(imm)) { m_formatter.oneByteOp(OP_GROUP1_EvIb, dst, GROUP1_OP_SUB); m_formatter.immediate8s(imm); @@ -1263,31 +1296,31 @@ public: #ifdef JS_CODEGEN_X64 void subq_rr(RegisterID src, RegisterID dst) { - spew("subq %s, %s", nameIReg(8,src), nameIReg(8,dst)); + spew("subq %s, %s", GPReg64Name(src), GPReg64Name(dst)); m_formatter.oneByteOp64(OP_SUB_GvEv, src, dst); } void subq_rm(RegisterID src, int32_t offset, RegisterID base) { - spew("subq %s, " MEM_ob, nameIReg(8,src), ADDR_ob(offset, base)); + spew("subq %s, " MEM_ob, GPReg64Name(src), ADDR_ob(offset, base)); m_formatter.oneByteOp64(OP_SUB_EvGv, offset, base, src); } void subq_mr(int32_t offset, RegisterID base, RegisterID dst) { - spew("subq " MEM_ob ", %s", ADDR_ob(offset, base), nameIReg(8,dst)); + spew("subq " MEM_ob ", %s", ADDR_ob(offset, base), GPReg64Name(dst)); m_formatter.oneByteOp64(OP_SUB_GvEv, offset, base, dst); } void subq_mr(const void* addr, RegisterID dst) { - spew("subq %p, %s", addr, nameIReg(8, dst)); + spew("subq %p, %s", addr, GPReg64Name(dst)); m_formatter.oneByteOp64(OP_SUB_GvEv, addr, dst); } void subq_ir(int32_t imm, RegisterID dst) { - spew("subq $%d, %s", imm, nameIReg(8,dst)); + spew("subq $%d, %s", imm, GPReg64Name(dst)); if (CAN_SIGN_EXTEND_8_32(imm)) { m_formatter.oneByteOp64(OP_GROUP1_EvIb, dst, GROUP1_OP_SUB); m_formatter.immediate8s(imm); @@ -1315,19 +1348,19 @@ public: void xorl_rr(RegisterID src, RegisterID dst) { - spew("xorl %s, %s", nameIReg(4,src), nameIReg(4,dst)); + spew("xorl %s, %s", GPReg32Name(src), GPReg32Name(dst)); m_formatter.oneByteOp(OP_XOR_GvEv, src, dst); } void xorl_mr(int32_t offset, RegisterID base, RegisterID dst) { - spew("xorl " MEM_ob ", %s", ADDR_ob(offset, base), nameIReg(4,dst)); + spew("xorl " MEM_ob ", %s", ADDR_ob(offset, base), GPReg32Name(dst)); m_formatter.oneByteOp(OP_XOR_GvEv, offset, base, dst); } void xorl_rm(RegisterID src, int32_t offset, RegisterID base) { - spew("xorl %s, " MEM_ob, nameIReg(4,src), ADDR_ob(offset, base)); + spew("xorl %s, " MEM_ob, GPReg32Name(src), ADDR_ob(offset, base)); m_formatter.oneByteOp(OP_XOR_EvGv, offset, base, src); } @@ -1345,7 +1378,7 @@ public: void xorl_ir(int32_t imm, RegisterID dst) { - spew("xorl $%d, %s", imm, nameIReg(4,dst)); + spew("xorl $%d, %s", imm, GPReg32Name(dst)); if (CAN_SIGN_EXTEND_8_32(imm)) { m_formatter.oneByteOp(OP_GROUP1_EvIb, dst, GROUP1_OP_XOR); m_formatter.immediate8s(imm); @@ -1361,13 +1394,13 @@ public: #ifdef JS_CODEGEN_X64 void xorq_rr(RegisterID src, RegisterID dst) { - spew("xorq %s, %s", nameIReg(8,src), nameIReg(8, dst)); + spew("xorq %s, %s", GPReg64Name(src), GPReg64Name(dst)); m_formatter.oneByteOp64(OP_XOR_GvEv, src, dst); } void xorq_ir(int32_t imm, RegisterID dst) { - spew("xorq $0x%" PRIx64 ", %s", int64_t(imm), nameIReg(8,dst)); + spew("xorq $0x%" PRIx64 ", %s", int64_t(imm), GPReg64Name(dst)); if (CAN_SIGN_EXTEND_8_32(imm)) { m_formatter.oneByteOp64(OP_GROUP1_EvIb, dst, GROUP1_OP_XOR); m_formatter.immediate8s(imm); @@ -1384,7 +1417,7 @@ public: void sarl_ir(int32_t imm, RegisterID dst) { MOZ_ASSERT(imm < 32); - spew("sarl $%d, %s", imm, nameIReg(4, dst)); + spew("sarl $%d, %s", imm, GPReg32Name(dst)); if (imm == 1) m_formatter.oneByteOp(OP_GROUP2_Ev1, dst, GROUP2_OP_SAR); else { @@ -1395,14 +1428,14 @@ public: void sarl_CLr(RegisterID dst) { - spew("sarl %%cl, %s", nameIReg(4, dst)); + spew("sarl %%cl, %s", GPReg32Name(dst)); m_formatter.oneByteOp(OP_GROUP2_EvCL, dst, GROUP2_OP_SAR); } void shrl_ir(int32_t imm, RegisterID dst) { MOZ_ASSERT(imm < 32); - spew("shrl $%d, %s", imm, nameIReg(4, dst)); + spew("shrl $%d, %s", imm, GPReg32Name(dst)); if (imm == 1) m_formatter.oneByteOp(OP_GROUP2_Ev1, dst, GROUP2_OP_SHR); else { @@ -1413,14 +1446,14 @@ public: void shrl_CLr(RegisterID dst) { - spew("shrl %%cl, %s", nameIReg(4, dst)); + spew("shrl %%cl, %s", GPReg32Name(dst)); m_formatter.oneByteOp(OP_GROUP2_EvCL, dst, GROUP2_OP_SHR); } void shll_ir(int32_t imm, RegisterID dst) { MOZ_ASSERT(imm < 32); - spew("shll $%d, %s", imm, nameIReg(4, dst)); + spew("shll $%d, %s", imm, GPReg32Name(dst)); if (imm == 1) m_formatter.oneByteOp(OP_GROUP2_Ev1, dst, GROUP2_OP_SHL); else { @@ -1431,7 +1464,7 @@ public: void shll_CLr(RegisterID dst) { - spew("shll %%cl, %s", nameIReg(4, dst)); + spew("shll %%cl, %s", GPReg32Name(dst)); m_formatter.oneByteOp(OP_GROUP2_EvCL, dst, GROUP2_OP_SHL); } @@ -1445,7 +1478,7 @@ public: void sarq_ir(int32_t imm, RegisterID dst) { MOZ_ASSERT(imm < 64); - spew("sarq $%d, %s", imm, nameIReg(8, dst)); + spew("sarq $%d, %s", imm, GPReg64Name(dst)); if (imm == 1) m_formatter.oneByteOp64(OP_GROUP2_Ev1, dst, GROUP2_OP_SAR); else { @@ -1457,7 +1490,7 @@ public: void shlq_ir(int32_t imm, RegisterID dst) { MOZ_ASSERT(imm < 64); - spew("shlq $%d, %s", imm, nameIReg(8, dst)); + spew("shlq $%d, %s", imm, GPReg64Name(dst)); if (imm == 1) m_formatter.oneByteOp64(OP_GROUP2_Ev1, dst, GROUP2_OP_SHL); else { @@ -1469,7 +1502,7 @@ public: void shrq_ir(int32_t imm, RegisterID dst) { MOZ_ASSERT(imm < 64); - spew("shrq $%d, %s", imm, nameIReg(8, dst)); + spew("shrq $%d, %s", imm, GPReg64Name(dst)); if (imm == 1) m_formatter.oneByteOp64(OP_GROUP2_Ev1, dst, GROUP2_OP_SHR); else { @@ -1481,31 +1514,31 @@ public: void bsr_rr(RegisterID src, RegisterID dst) { - spew("bsr %s, %s", nameIReg(4, src), nameIReg(4, dst)); + spew("bsr %s, %s", GPReg32Name(src), GPReg32Name(dst)); m_formatter.twoByteOp(OP2_BSR_GvEv, src, dst); } void imull_rr(RegisterID src, RegisterID dst) { - spew("imull %s, %s", nameIReg(4,src), nameIReg(4, dst)); + spew("imull %s, %s", GPReg32Name(src), GPReg32Name(dst)); m_formatter.twoByteOp(OP2_IMUL_GvEv, src, dst); } void imull_r(RegisterID multiplier) { - spew("imull %s", nameIReg(4, multiplier)); + spew("imull %s", GPReg32Name(multiplier)); m_formatter.oneByteOp(OP_GROUP3_Ev, multiplier, GROUP3_OP_IMUL); } void imull_mr(int32_t offset, RegisterID base, RegisterID dst) { - spew("imull " MEM_ob ", %s", ADDR_ob(offset, base), nameIReg(4,dst)); + spew("imull " MEM_ob ", %s", ADDR_ob(offset, base), GPReg32Name(dst)); m_formatter.twoByteOp(OP2_IMUL_GvEv, offset, base, dst); } void imull_ir(int32_t value, RegisterID src, RegisterID dst) { - spew("imull $%d, %s, %s", value, nameIReg(4, src), nameIReg(4, dst)); + spew("imull $%d, %s, %s", value, GPReg32Name(src), GPReg32Name(dst)); if (CAN_SIGN_EXTEND_8_32(value)) { m_formatter.oneByteOp(OP_IMUL_GvEvIb, src, dst); m_formatter.immediate8s(value); @@ -1517,13 +1550,13 @@ public: void idivl_r(RegisterID divisor) { - spew("idivl %s", nameIReg(4, divisor)); + spew("idivl %s", GPReg32Name(divisor)); m_formatter.oneByteOp(OP_GROUP3_Ev, divisor, GROUP3_OP_IDIV); } void divl_r(RegisterID divisor) { - spew("div %s", nameIReg(4, divisor)); + spew("div %s", GPReg32Name(divisor)); m_formatter.oneByteOp(OP_GROUP3_Ev, divisor, GROUP3_OP_DIV); } @@ -1557,34 +1590,34 @@ public: void cmpxchg8(RegisterID src, int32_t offset, RegisterID base) { - spew("cmpxchg8 %s, " MEM_ob, nameIReg(src), ADDR_ob(offset, base)); + spew("cmpxchg8 %s, " MEM_ob, GPRegName(src), ADDR_ob(offset, base)); m_formatter.twoByteOp(OP2_CMPXCHG_GvEb, offset, base, src); } void cmpxchg8(RegisterID src, int32_t offset, RegisterID base, RegisterID index, int scale) { - spew("cmpxchg8 %s, " MEM_obs, nameIReg(src), ADDR_obs(offset, base, index, scale)); + spew("cmpxchg8 %s, " MEM_obs, GPRegName(src), ADDR_obs(offset, base, index, scale)); m_formatter.twoByteOp(OP2_CMPXCHG_GvEb, offset, base, index, scale, src); } void cmpxchg16(RegisterID src, int32_t offset, RegisterID base) { - spew("cmpxchg16 %s, " MEM_ob, nameIReg(src), ADDR_ob(offset, base)); + spew("cmpxchg16 %s, " MEM_ob, GPRegName(src), ADDR_ob(offset, base)); m_formatter.prefix(PRE_OPERAND_SIZE); m_formatter.twoByteOp(OP2_CMPXCHG_GvEw, offset, base, src); } void cmpxchg16(RegisterID src, int32_t offset, RegisterID base, RegisterID index, int scale) { - spew("cmpxchg16 %s, " MEM_obs, nameIReg(src), ADDR_obs(offset, base, index, scale)); + spew("cmpxchg16 %s, " MEM_obs, GPRegName(src), ADDR_obs(offset, base, index, scale)); m_formatter.prefix(PRE_OPERAND_SIZE); m_formatter.twoByteOp(OP2_CMPXCHG_GvEw, offset, base, index, scale, src); } void cmpxchg32(RegisterID src, int32_t offset, RegisterID base) { - spew("cmpxchg32 %s, " MEM_ob, nameIReg(src), ADDR_ob(offset, base)); + spew("cmpxchg32 %s, " MEM_ob, GPRegName(src), ADDR_ob(offset, base)); m_formatter.twoByteOp(OP2_CMPXCHG_GvEw, offset, base, src); } void cmpxchg32(RegisterID src, int32_t offset, RegisterID base, RegisterID index, int scale) { - spew("cmpxchg32 %s, " MEM_obs, nameIReg(src), ADDR_obs(offset, base, index, scale)); + spew("cmpxchg32 %s, " MEM_obs, GPRegName(src), ADDR_obs(offset, base, index, scale)); m_formatter.twoByteOp(OP2_CMPXCHG_GvEw, offset, base, index, scale, src); } @@ -1593,25 +1626,25 @@ public: void cmpl_rr(RegisterID rhs, RegisterID lhs) { - spew("cmpl %s, %s", nameIReg(4, rhs), nameIReg(4, lhs)); + spew("cmpl %s, %s", GPReg32Name(rhs), GPReg32Name(lhs)); m_formatter.oneByteOp(OP_CMP_GvEv, rhs, lhs); } void cmpl_rm(RegisterID rhs, int32_t offset, RegisterID base) { - spew("cmpl %s, " MEM_ob, nameIReg(4, rhs), ADDR_ob(offset, base)); + spew("cmpl %s, " MEM_ob, GPReg32Name(rhs), ADDR_ob(offset, base)); m_formatter.oneByteOp(OP_CMP_EvGv, offset, base, rhs); } void cmpl_mr(int32_t offset, RegisterID base, RegisterID lhs) { - spew("cmpl " MEM_ob ", %s", ADDR_ob(offset, base), nameIReg(4, lhs)); + spew("cmpl " MEM_ob ", %s", ADDR_ob(offset, base), GPReg32Name(lhs)); m_formatter.oneByteOp(OP_CMP_GvEv, offset, base, lhs); } void cmpl_mr(const void *address, RegisterID lhs) { - spew("cmpl %p, %s", address, nameIReg(4, lhs)); + spew("cmpl %p, %s", address, GPReg32Name(lhs)); m_formatter.oneByteOp(OP_CMP_GvEv, address, lhs); } @@ -1622,7 +1655,7 @@ public: return; } - spew("cmpl $0x%x, %s", rhs, nameIReg(4, lhs)); + spew("cmpl $0x%x, %s", rhs, GPReg32Name(lhs)); if (CAN_SIGN_EXTEND_8_32(rhs)) { m_formatter.oneByteOp(OP_GROUP1_EvIb, lhs, GROUP1_OP_CMP); m_formatter.immediate8s(rhs); @@ -1637,7 +1670,7 @@ public: void cmpl_i32r(int rhs, RegisterID lhs) { - spew("cmpl $0x%04x, %s", rhs, nameIReg(4, lhs)); + spew("cmpl $0x%04x, %s", rhs, GPReg32Name(lhs)); if (lhs == X86Registers::eax) m_formatter.oneByteOp(OP_CMP_EAXIv); else @@ -1734,19 +1767,19 @@ public: #ifdef JS_CODEGEN_X64 void cmpq_rr(RegisterID rhs, RegisterID lhs) { - spew("cmpq %s, %s", nameIReg(8, rhs), nameIReg(8, lhs)); + spew("cmpq %s, %s", GPReg64Name(rhs), GPReg64Name(lhs)); m_formatter.oneByteOp64(OP_CMP_GvEv, rhs, lhs); } void cmpq_rm(RegisterID rhs, int32_t offset, RegisterID base) { - spew("cmpq %s, " MEM_ob, nameIReg(8, rhs), ADDR_ob(offset, base)); + spew("cmpq %s, " MEM_ob, GPReg64Name(rhs), ADDR_ob(offset, base)); m_formatter.oneByteOp64(OP_CMP_EvGv, offset, base, rhs); } void cmpq_mr(int32_t offset, RegisterID base, RegisterID lhs) { - spew("cmpq " MEM_ob ", %s", ADDR_ob(offset, base), nameIReg(8, lhs)); + spew("cmpq " MEM_ob ", %s", ADDR_ob(offset, base), GPReg64Name(lhs)); m_formatter.oneByteOp64(OP_CMP_GvEv, offset, base, lhs); } @@ -1757,7 +1790,7 @@ public: return; } - spew("cmpq $0x%" PRIx64 ", %s", int64_t(rhs), nameIReg(8, lhs)); + spew("cmpq $0x%" PRIx64 ", %s", int64_t(rhs), GPReg64Name(lhs)); if (CAN_SIGN_EXTEND_8_32(rhs)) { m_formatter.oneByteOp64(OP_GROUP1_EvIb, lhs, GROUP1_OP_CMP); m_formatter.immediate8s(rhs); @@ -1806,19 +1839,19 @@ public: } void cmpq_rm(RegisterID rhs, const void* addr) { - spew("cmpq %s, %p", nameIReg(8, rhs), addr); + spew("cmpq %s, %p", GPReg64Name(rhs), addr); m_formatter.oneByteOp64(OP_CMP_EvGv, addr, rhs); } #endif void cmpl_rm(RegisterID rhs, const void* addr) { - spew("cmpl %s, %p", nameIReg(4, rhs), addr); + spew("cmpl %s, %p", GPReg32Name(rhs), addr); m_formatter.oneByteOp(OP_CMP_EvGv, addr, rhs); } void cmpl_rm_disp32(RegisterID rhs, const void* addr) { - spew("cmpl %s, %p", nameIReg(4, rhs), addr); + spew("cmpl %s, %p", GPReg32Name(rhs), addr); m_formatter.oneByteOp_disp32(OP_CMP_EvGv, addr, rhs); } @@ -1836,7 +1869,7 @@ public: void cmpw_rr(RegisterID rhs, RegisterID lhs) { - spew("cmpw %s, %s", nameIReg(2, rhs), nameIReg(2, lhs)); + spew("cmpw %s, %s", GPReg16Name(rhs), GPReg16Name(lhs)); m_formatter.prefix(PRE_OPERAND_SIZE); m_formatter.oneByteOp(OP_CMP_GvEv, rhs, lhs); } @@ -1864,13 +1897,13 @@ public: void testl_rr(RegisterID rhs, RegisterID lhs) { - spew("testl %s, %s", nameIReg(4, rhs), nameIReg(4, lhs)); + spew("testl %s, %s", GPReg32Name(rhs), GPReg32Name(lhs)); m_formatter.oneByteOp(OP_TEST_EvGv, lhs, rhs); } void testb_rr(RegisterID rhs, RegisterID lhs) { - spew("testb %s, %s", nameIReg(1, rhs), nameIReg(1, lhs)); + spew("testb %s, %s", GPReg8Name(rhs), GPReg8Name(lhs)); m_formatter.oneByteOp(OP_TEST_EbGb, lhs, rhs); } @@ -1888,7 +1921,7 @@ public: testb_ir_norex(rhs >> 8, X86Registers::getSubregH(lhs)); return; } - spew("testl $0x%x, %s", rhs, nameIReg(4, lhs)); + spew("testl $0x%x, %s", rhs, GPReg32Name(lhs)); if (lhs == X86Registers::eax) m_formatter.oneByteOp(OP_TEST_EAXIv); else @@ -1934,7 +1967,7 @@ public: #ifdef JS_CODEGEN_X64 void testq_rr(RegisterID rhs, RegisterID lhs) { - spew("testq %s, %s", nameIReg(8, rhs), nameIReg(8, lhs)); + spew("testq %s, %s", GPReg64Name(rhs), GPReg64Name(lhs)); m_formatter.oneByteOp64(OP_TEST_EvGv, lhs, rhs); } @@ -1946,7 +1979,7 @@ public: testl_ir(rhs, lhs); return; } - spew("testq $0x%" PRIx64 ", %s", int64_t(rhs), nameIReg(8, lhs)); + spew("testq $0x%" PRIx64 ", %s", int64_t(rhs), GPReg64Name(lhs)); if (lhs == X86Registers::eax) m_formatter.oneByteOp64(OP_TEST_EAXIv); else @@ -1978,7 +2011,7 @@ public: void testb_ir(int rhs, RegisterID lhs) { - spew("testb $0x%x, %s", rhs, nameIReg(1, lhs)); + spew("testb $0x%x, %s", rhs, GPReg8Name(lhs)); if (lhs == X86Registers::eax) m_formatter.oneByteOp8(OP_TEST_EAXIb); else @@ -1990,14 +2023,14 @@ public: // reference ah..bh. void testb_ir_norex(int rhs, RegisterID lhs) { - spew("testb $0x%x, %s", rhs, nameI8Reg_norex(lhs)); + spew("testb $0x%x, %s", rhs, GPReg8Name_norex(lhs)); m_formatter.oneByteOp8_norex(OP_GROUP3_EbIb, lhs, GROUP3_OP_TEST); m_formatter.immediate8(rhs); } void setCC_r(Condition cond, RegisterID lhs) { - spew("set%s %s", nameCC(cond), nameIReg(1, lhs)); + spew("set%s %s", nameCC(cond), GPReg8Name(lhs)); m_formatter.twoByteOp8(setccOpcode(cond), lhs, (GroupOpcodeID)0); } @@ -2031,67 +2064,67 @@ public: void xchgl_rr(RegisterID src, RegisterID dst) { - spew("xchgl %s, %s", nameIReg(4,src), nameIReg(4,dst)); + spew("xchgl %s, %s", GPReg32Name(src), GPReg32Name(dst)); m_formatter.oneByteOp(OP_XCHG_GvEv, src, dst); } #ifdef JS_CODEGEN_X64 void xchgq_rr(RegisterID src, RegisterID dst) { - spew("xchgq %s, %s", nameIReg(8,src), nameIReg(8,dst)); + spew("xchgq %s, %s", GPReg64Name(src), GPReg64Name(dst)); m_formatter.oneByteOp64(OP_XCHG_GvEv, src, dst); } #endif void movl_rr(RegisterID src, RegisterID dst) { - spew("movl %s, %s", nameIReg(4,src), nameIReg(4,dst)); + spew("movl %s, %s", GPReg32Name(src), GPReg32Name(dst)); m_formatter.oneByteOp(OP_MOV_GvEv, src, dst); } void movw_rm(RegisterID src, int32_t offset, RegisterID base) { - spew("movw %s, " MEM_ob, nameIReg(2,src), ADDR_ob(offset, base)); + spew("movw %s, " MEM_ob, GPReg16Name(src), ADDR_ob(offset, base)); m_formatter.prefix(PRE_OPERAND_SIZE); m_formatter.oneByteOp(OP_MOV_EvGv, offset, base, src); } void movw_rm_disp32(RegisterID src, int32_t offset, RegisterID base) { - spew("movw %s, " MEM_o32b, nameIReg(2,src), ADDR_o32b(offset, base)); + spew("movw %s, " MEM_o32b, GPReg16Name(src), ADDR_o32b(offset, base)); m_formatter.prefix(PRE_OPERAND_SIZE); m_formatter.oneByteOp_disp32(OP_MOV_EvGv, offset, base, src); } void movw_rm(RegisterID src, int32_t offset, RegisterID base, RegisterID index, int scale) { - spew("movw %s, " MEM_obs, nameIReg(2, src), ADDR_obs(offset, base, index, scale)); + spew("movw %s, " MEM_obs, GPReg16Name(src), ADDR_obs(offset, base, index, scale)); m_formatter.prefix(PRE_OPERAND_SIZE); m_formatter.oneByteOp(OP_MOV_EvGv, offset, base, index, scale, src); } void movw_rm(RegisterID src, const void* addr) { - spew("movw %s, %p", nameIReg(2, src), addr); + spew("movw %s, %p", GPReg16Name(src), addr); m_formatter.prefix(PRE_OPERAND_SIZE); m_formatter.oneByteOp_disp32(OP_MOV_EvGv, addr, src); } void movl_rm(RegisterID src, int32_t offset, RegisterID base) { - spew("movl %s, " MEM_ob, nameIReg(4,src), ADDR_ob(offset, base)); + spew("movl %s, " MEM_ob, GPReg32Name(src), ADDR_ob(offset, base)); m_formatter.oneByteOp(OP_MOV_EvGv, offset, base, src); } void movl_rm_disp32(RegisterID src, int32_t offset, RegisterID base) { - spew("movl %s, " MEM_o32b, nameIReg(4,src), ADDR_o32b(offset, base)); + spew("movl %s, " MEM_o32b, GPReg32Name(src), ADDR_o32b(offset, base)); m_formatter.oneByteOp_disp32(OP_MOV_EvGv, offset, base, src); } void movl_rm(RegisterID src, int32_t offset, RegisterID base, RegisterID index, int scale) { - spew("movl %s, " MEM_obs, nameIReg(4, src), ADDR_obs(offset, base, index, scale)); + spew("movl %s, " MEM_obs, GPReg32Name(src), ADDR_obs(offset, base, index, scale)); m_formatter.oneByteOp(OP_MOV_EvGv, offset, base, index, scale, src); } @@ -2119,13 +2152,13 @@ public: void movl_mr(int32_t offset, RegisterID base, RegisterID dst) { - spew("movl " MEM_ob ", %s", ADDR_ob(offset, base), nameIReg(4, dst)); + spew("movl " MEM_ob ", %s", ADDR_ob(offset, base), GPReg32Name(dst)); m_formatter.oneByteOp(OP_MOV_GvEv, offset, base, dst); } void movl_mr_disp32(int32_t offset, RegisterID base, RegisterID dst) { - spew("movl " MEM_o32b ", %s", ADDR_o32b(offset, base), nameIReg(4,dst)); + spew("movl " MEM_o32b ", %s", ADDR_o32b(offset, base), GPReg32Name(dst)); m_formatter.oneByteOp_disp32(OP_MOV_GvEv, offset, base, dst); } @@ -2133,13 +2166,13 @@ public: { int32_t disp = addressImmediate(base); - spew("movl " MEM_os ", %s", ADDR_os(disp, index, scale), nameIReg(4, dst)); + spew("movl " MEM_os ", %s", ADDR_os(disp, index, scale), GPReg32Name(dst)); m_formatter.oneByteOp_disp32(OP_MOV_GvEv, disp, index, scale, dst); } void movl_mr(int32_t offset, RegisterID base, RegisterID index, int scale, RegisterID dst) { - spew("movl " MEM_obs ", %s", ADDR_obs(offset, base, index, scale), nameIReg(4, dst)); + spew("movl " MEM_obs ", %s", ADDR_obs(offset, base, index, scale), GPReg32Name(dst)); m_formatter.oneByteOp(OP_MOV_GvEv, offset, base, index, scale, dst); } @@ -2155,20 +2188,20 @@ public: return; } - spew("movl %p, %s", addr, nameIReg(4, dst)); + spew("movl %p, %s", addr, GPReg32Name(dst)); m_formatter.oneByteOp(OP_MOV_GvEv, addr, dst); } void movl_i32r(int32_t imm, RegisterID dst) { - spew("movl $0x%x, %s", imm, nameIReg(4, dst)); + spew("movl $0x%x, %s", imm, GPReg32Name(dst)); m_formatter.oneByteOp(OP_MOV_EAXIv, dst); m_formatter.immediate32(imm); } void movb_ir(int32_t imm, RegisterID reg) { - spew("movb $0x%x, %s", imm, nameIReg(1, reg)); + spew("movb $0x%x, %s", imm, GPReg8Name(reg)); m_formatter.oneByteOp(OP_MOV_EbGv, reg); m_formatter.immediate8(imm); } @@ -2253,13 +2286,13 @@ public: #ifdef JS_CODEGEN_X64 void movq_rr(RegisterID src, RegisterID dst) { - spew("movq %s, %s", nameIReg(8,src), nameIReg(8,dst)); + spew("movq %s, %s", GPReg64Name(src), GPReg64Name(dst)); m_formatter.oneByteOp64(OP_MOV_GvEv, src, dst); } void movq_rm(RegisterID src, int32_t offset, RegisterID base) { - spew("movq %s, " MEM_ob, nameIReg(8,src), ADDR_ob(offset, base)); + spew("movq %s, " MEM_ob, GPReg64Name(src), ADDR_ob(offset, base)); m_formatter.oneByteOp64(OP_MOV_EvGv, offset, base, src); } @@ -2271,7 +2304,7 @@ public: void movq_rm(RegisterID src, int32_t offset, RegisterID base, RegisterID index, int scale) { - spew("movq %s, " MEM_obs, nameIReg(8,src), ADDR_obs(offset, base, index, scale)); + spew("movq %s, " MEM_obs, GPReg64Name(src), ADDR_obs(offset, base, index, scale)); m_formatter.oneByteOp64(OP_MOV_EvGv, offset, base, index, scale, src); } @@ -2282,7 +2315,7 @@ public: return; } - spew("movq %s, %p", nameIReg(8, src), addr); + spew("movq %s, %p", GPReg64Name(src), addr); m_formatter.oneByteOp64(OP_MOV_EvGv, addr, src); } @@ -2312,7 +2345,7 @@ public: void movq_mr(int32_t offset, RegisterID base, RegisterID dst) { - spew("movq " MEM_ob ", %s", ADDR_ob(offset, base), nameIReg(8,dst)); + spew("movq " MEM_ob ", %s", ADDR_ob(offset, base), GPReg64Name(dst)); m_formatter.oneByteOp64(OP_MOV_GvEv, offset, base, dst); } @@ -2324,7 +2357,7 @@ public: void movq_mr(int32_t offset, RegisterID base, RegisterID index, int scale, RegisterID dst) { - spew("movq " MEM_obs ", %s", ADDR_obs(offset, base, index, scale), nameIReg(8,dst)); + spew("movq " MEM_obs ", %s", ADDR_obs(offset, base, index, scale), GPReg64Name(dst)); m_formatter.oneByteOp64(OP_MOV_GvEv, offset, base, index, scale, dst); } @@ -2335,13 +2368,13 @@ public: return; } - spew("movq %p, %s", addr, nameIReg(8, dst)); + spew("movq %p, %s", addr, GPReg64Name(dst)); m_formatter.oneByteOp64(OP_MOV_GvEv, addr, dst); } void leaq_mr(int32_t offset, RegisterID base, RegisterID index, int scale, RegisterID dst) { - spew("leaq " MEM_obs ", %s", ADDR_obs(offset, base, index, scale), nameIReg(8,dst)), + spew("leaq " MEM_obs ", %s", ADDR_obs(offset, base, index, scale), GPReg64Name(dst)), m_formatter.oneByteOp64(OP_LEA, offset, base, index, scale, dst); } @@ -2372,21 +2405,21 @@ public: // movl_i32r *zero*-extends its 32-bit immediate, and it has smaller code // size, so it's preferred for values which could use either. void movq_i32r(int32_t imm, RegisterID dst) { - spew("movq $%d, %s", imm, nameIReg(dst)); + spew("movq $%d, %s", imm, GPRegName(dst)); m_formatter.oneByteOp64(OP_GROUP11_EvIz, dst, GROUP11_MOV); m_formatter.immediate32(imm); } void movq_i64r(int64_t imm, RegisterID dst) { - spew("movabsq $0x%" PRIx64 ", %s", imm, nameIReg(8, dst)); + spew("movabsq $0x%" PRIx64 ", %s", imm, GPReg64Name(dst)); m_formatter.oneByteOp64(OP_MOV_EAXIv, dst); m_formatter.immediate64(imm); } void movsxd_rr(RegisterID src, RegisterID dst) { - spew("movsxd %s, %s", nameIReg(4, src), nameIReg(8, dst)); + spew("movsxd %s, %s", GPReg32Name(src), GPReg64Name(dst)); m_formatter.oneByteOp64(OP_MOVSXD_GvEv, src, dst); } @@ -2395,7 +2428,7 @@ public: { m_formatter.oneByteRipOp(OP_MOV_GvEv, 0, (RegisterID)dst); JmpSrc label(m_formatter.size()); - spew("movl .Lfrom%d(%%rip), %s", label.offset(), nameIReg(4, dst)); + spew("movl .Lfrom%d(%%rip), %s", label.offset(), GPReg32Name(dst)); return label; } @@ -2404,7 +2437,7 @@ public: { m_formatter.oneByteRipOp(OP_MOV_EvGv, 0, (RegisterID)src); JmpSrc label(m_formatter.size()); - spew("movl %s, .Lfrom%d(%%rip)", nameIReg(4, src), label.offset()); + spew("movl %s, .Lfrom%d(%%rip)", GPReg32Name(src), label.offset()); return label; } @@ -2413,7 +2446,7 @@ public: { m_formatter.oneByteRipOp64(OP_MOV_GvEv, 0, dst); JmpSrc label(m_formatter.size()); - spew("movq .Lfrom%d(%%rip), %s", label.offset(), nameIReg(dst)); + spew("movq .Lfrom%d(%%rip), %s", label.offset(), GPRegName(dst)); return label; } #endif @@ -2428,7 +2461,7 @@ public: return; } - spew("movl %s, %p", nameIReg(4, src), addr); + spew("movl %s, %p", GPReg32Name(src), addr); m_formatter.oneByteOp(OP_MOV_EvGv, addr, src); } @@ -2441,175 +2474,175 @@ public: void movb_rm(RegisterID src, int32_t offset, RegisterID base) { - spew("movb %s, " MEM_ob, nameIReg(1, src), ADDR_ob(offset, base)); + spew("movb %s, " MEM_ob, GPReg8Name(src), ADDR_ob(offset, base)); m_formatter.oneByteOp8(OP_MOV_EbGv, offset, base, src); } void movb_rm_disp32(RegisterID src, int32_t offset, RegisterID base) { - spew("movb %s, " MEM_o32b, nameIReg(1, src), ADDR_o32b(offset, base)); + spew("movb %s, " MEM_o32b, GPReg8Name(src), ADDR_o32b(offset, base)); m_formatter.oneByteOp8_disp32(OP_MOV_EbGv, offset, base, src); } void movb_rm(RegisterID src, int32_t offset, RegisterID base, RegisterID index, int scale) { - spew("movb %s, " MEM_obs, nameIReg(1, src), ADDR_obs(offset, base, index, scale)); + spew("movb %s, " MEM_obs, GPReg8Name(src), ADDR_obs(offset, base, index, scale)); m_formatter.oneByteOp8(OP_MOV_EbGv, offset, base, index, scale, src); } void movb_rm(RegisterID src, const void* addr) { - spew("movb %s, %p", nameIReg(1, src), addr); + spew("movb %s, %p", GPReg8Name(src), addr); m_formatter.oneByteOp8(OP_MOV_EbGv, addr, src); } void movb_mr(int32_t offset, RegisterID base, RegisterID dst) { - spew("movb " MEM_ob ", %s", ADDR_ob(offset, base), nameIReg(1, dst)); + spew("movb " MEM_ob ", %s", ADDR_ob(offset, base), GPReg8Name(dst)); m_formatter.oneByteOp(OP_MOV_GvEb, offset, base, dst); } void movb_mr(int32_t offset, RegisterID base, RegisterID index, int scale, RegisterID dst) { - spew("movb " MEM_obs ", %s", ADDR_obs(offset, base, index, scale), nameIReg(1, dst)); + spew("movb " MEM_obs ", %s", ADDR_obs(offset, base, index, scale), GPReg8Name(dst)); m_formatter.oneByteOp(OP_MOV_GvEb, offset, base, index, scale, dst); } void movzbl_mr(int32_t offset, RegisterID base, RegisterID dst) { - spew("movzbl " MEM_ob ", %s", ADDR_ob(offset, base), nameIReg(4, dst)); + spew("movzbl " MEM_ob ", %s", ADDR_ob(offset, base), GPReg32Name(dst)); m_formatter.twoByteOp(OP2_MOVZX_GvEb, offset, base, dst); } void movzbl_mr_disp32(int32_t offset, RegisterID base, RegisterID dst) { - spew("movzbl " MEM_o32b ", %s", ADDR_o32b(offset, base), nameIReg(4, dst)); + spew("movzbl " MEM_o32b ", %s", ADDR_o32b(offset, base), GPReg32Name(dst)); m_formatter.twoByteOp_disp32(OP2_MOVZX_GvEb, offset, base, dst); } void movzbl_mr(int32_t offset, RegisterID base, RegisterID index, int scale, RegisterID dst) { - spew("movzbl " MEM_obs ", %s", ADDR_obs(offset, base, index, scale), nameIReg(4, dst)); + spew("movzbl " MEM_obs ", %s", ADDR_obs(offset, base, index, scale), GPReg32Name(dst)); m_formatter.twoByteOp(OP2_MOVZX_GvEb, offset, base, index, scale, dst); } void movzbl_mr(const void* addr, RegisterID dst) { - spew("movzbl %p, %s", addr, nameIReg(4, dst)); + spew("movzbl %p, %s", addr, GPReg32Name(dst)); m_formatter.twoByteOp(OP2_MOVZX_GvEb, addr, dst); } void movsbl_rr(RegisterID src, RegisterID dst) { - spew("movsbl %s, %s", nameIReg(1,src), nameIReg(4,dst)); + spew("movsbl %s, %s", GPReg8Name(src), GPReg32Name(dst)); m_formatter.twoByteOp8_movx(OP2_MOVSX_GvEb, src, dst); } void movsbl_mr(int32_t offset, RegisterID base, RegisterID dst) { - spew("movsbl " MEM_ob ", %s", ADDR_ob(offset, base), nameIReg(4, dst)); + spew("movsbl " MEM_ob ", %s", ADDR_ob(offset, base), GPReg32Name(dst)); m_formatter.twoByteOp(OP2_MOVSX_GvEb, offset, base, dst); } void movsbl_mr_disp32(int32_t offset, RegisterID base, RegisterID dst) { - spew("movsbl " MEM_o32b ", %s", ADDR_o32b(offset, base), nameIReg(4, dst)); + spew("movsbl " MEM_o32b ", %s", ADDR_o32b(offset, base), GPReg32Name(dst)); m_formatter.twoByteOp_disp32(OP2_MOVSX_GvEb, offset, base, dst); } void movsbl_mr(int32_t offset, RegisterID base, RegisterID index, int scale, RegisterID dst) { - spew("movsbl " MEM_obs ", %s", ADDR_obs(offset, base, index, scale), nameIReg(4, dst)); + spew("movsbl " MEM_obs ", %s", ADDR_obs(offset, base, index, scale), GPReg32Name(dst)); m_formatter.twoByteOp(OP2_MOVSX_GvEb, offset, base, index, scale, dst); } void movsbl_mr(const void* addr, RegisterID dst) { - spew("movsbl %p, %s", addr, nameIReg(4, dst)); + spew("movsbl %p, %s", addr, GPReg32Name(dst)); m_formatter.twoByteOp(OP2_MOVSX_GvEb, addr, dst); } void movzwl_rr(RegisterID src, RegisterID dst) { - spew("movzwl %s, %s", nameIReg(2, src), nameIReg(4, dst)); + spew("movzwl %s, %s", GPReg16Name(src), GPReg32Name(dst)); m_formatter.twoByteOp(OP2_MOVZX_GvEw, src, dst); } void movzwl_mr(int32_t offset, RegisterID base, RegisterID dst) { - spew("movzwl " MEM_ob ", %s", ADDR_ob(offset, base), nameIReg(4, dst)); + spew("movzwl " MEM_ob ", %s", ADDR_ob(offset, base), GPReg32Name(dst)); m_formatter.twoByteOp(OP2_MOVZX_GvEw, offset, base, dst); } void movzwl_mr_disp32(int32_t offset, RegisterID base, RegisterID dst) { - spew("movzwl " MEM_o32b ", %s", ADDR_o32b(offset, base), nameIReg(4, dst)); + spew("movzwl " MEM_o32b ", %s", ADDR_o32b(offset, base), GPReg32Name(dst)); m_formatter.twoByteOp_disp32(OP2_MOVZX_GvEw, offset, base, dst); } void movzwl_mr(int32_t offset, RegisterID base, RegisterID index, int scale, RegisterID dst) { - spew("movzwl " MEM_obs ", %s", ADDR_obs(offset, base, index, scale), nameIReg(4, dst)); + spew("movzwl " MEM_obs ", %s", ADDR_obs(offset, base, index, scale), GPReg32Name(dst)); m_formatter.twoByteOp(OP2_MOVZX_GvEw, offset, base, index, scale, dst); } void movzwl_mr(const void* addr, RegisterID dst) { - spew("movzwl %p, %s", addr, nameIReg(4, dst)); + spew("movzwl %p, %s", addr, GPReg32Name(dst)); m_formatter.twoByteOp(OP2_MOVZX_GvEw, addr, dst); } void movswl_rr(RegisterID src, RegisterID dst) { - spew("movswl %s, %s", nameIReg(2, src), nameIReg(4, dst)); + spew("movswl %s, %s", GPReg16Name(src), GPReg32Name(dst)); m_formatter.twoByteOp(OP2_MOVSX_GvEw, src, dst); } void movswl_mr(int32_t offset, RegisterID base, RegisterID dst) { - spew("movswl " MEM_ob ", %s", ADDR_ob(offset, base), nameIReg(4, dst)); + spew("movswl " MEM_ob ", %s", ADDR_ob(offset, base), GPReg32Name(dst)); m_formatter.twoByteOp(OP2_MOVSX_GvEw, offset, base, dst); } void movswl_mr_disp32(int32_t offset, RegisterID base, RegisterID dst) { - spew("movswl " MEM_o32b ", %s", ADDR_o32b(offset, base), nameIReg(4, dst)); + spew("movswl " MEM_o32b ", %s", ADDR_o32b(offset, base), GPReg32Name(dst)); m_formatter.twoByteOp_disp32(OP2_MOVSX_GvEw, offset, base, dst); } void movswl_mr(int32_t offset, RegisterID base, RegisterID index, int scale, RegisterID dst) { - spew("movswl " MEM_obs ", %s", ADDR_obs(offset, base, index, scale), nameIReg(4, dst)); + spew("movswl " MEM_obs ", %s", ADDR_obs(offset, base, index, scale), GPReg32Name(dst)); m_formatter.twoByteOp(OP2_MOVSX_GvEw, offset, base, index, scale, dst); } void movswl_mr(const void* addr, RegisterID dst) { - spew("movswl %p, %s", addr, nameIReg(4, dst)); + spew("movswl %p, %s", addr, GPReg32Name(dst)); m_formatter.twoByteOp(OP2_MOVSX_GvEw, addr, dst); } void movzbl_rr(RegisterID src, RegisterID dst) { - spew("movzbl %s, %s", nameIReg(1,src), nameIReg(4,dst)); + spew("movzbl %s, %s", GPReg8Name(src), GPReg32Name(dst)); m_formatter.twoByteOp8_movx(OP2_MOVZX_GvEb, src, dst); } void leal_mr(int32_t offset, RegisterID base, RegisterID index, int scale, RegisterID dst) { - spew("leal " MEM_obs ", %s", ADDR_obs(offset, base, index, scale), nameIReg(4, dst)); + spew("leal " MEM_obs ", %s", ADDR_obs(offset, base, index, scale), GPReg32Name(dst)); m_formatter.oneByteOp(OP_LEA, offset, base, index, scale, dst); } void leal_mr(int32_t offset, RegisterID base, RegisterID dst) { - spew("leal " MEM_ob ", %s", ADDR_ob(offset, base), nameIReg(4,dst)); + spew("leal " MEM_ob ", %s", ADDR_ob(offset, base), GPReg32Name(dst)); m_formatter.oneByteOp(OP_LEA, offset, base, dst); } #ifdef JS_CODEGEN_X64 void leaq_mr(int32_t offset, RegisterID base, RegisterID dst) { - spew("leaq " MEM_ob ", %s", ADDR_ob(offset, base), nameIReg(8,dst)); + spew("leaq " MEM_ob ", %s", ADDR_ob(offset, base), GPReg64Name(dst)); m_formatter.oneByteOp64(OP_LEA, offset, base, dst); } @@ -2618,7 +2651,7 @@ public: { m_formatter.oneByteRipOp64(OP_LEA, 0, dst); JmpSrc label(m_formatter.size()); - spew("leaq .Lfrom%d(%%rip), %s", label.offset(), nameIReg(dst)); + spew("leaq .Lfrom%d(%%rip), %s", label.offset(), GPRegName(dst)); return label; } #endif @@ -2637,7 +2670,7 @@ public: void call_r(RegisterID dst) { m_formatter.oneByteOp(OP_GROUP5_Ev, dst, GROUP5_OP_CALLN); - spew("call *%s", nameIReg(dst)); + spew("call *%s", GPRegName(dst)); } void call_m(int32_t offset, RegisterID base) @@ -2685,7 +2718,7 @@ public: void jmp_r(RegisterID dst) { - spew("jmp *%s", nameIReg(dst)); + spew("jmp *%s", GPRegName(dst)); m_formatter.oneByteOp(OP_GROUP5_Ev, dst, GROUP5_OP_JMPN); } @@ -3657,7 +3690,7 @@ public: void vpextrd_irm(unsigned lane, XMMRegisterID src, int32_t offset, RegisterID base) { MOZ_ASSERT(lane < 4); - spew("pextrd $0x%x, %s, " MEM_ob, lane, nameFPReg(src), ADDR_ob(offset, base)); + spew("pextrd $0x%x, %s, " MEM_ob, lane, XMMRegName(src), ADDR_ob(offset, base)); m_formatter.prefix(PRE_SSE_66); m_formatter.threeByteOp(OP3_PEXTRD_EdVdqIb, ESCAPE_PEXTRD, offset, base, (RegisterID)src); m_formatter.immediate8u(lane); @@ -4026,9 +4059,9 @@ private: m_formatter.twoByteRipOp(opcode, ripOffset, dst); JmpSrc label(m_formatter.size()); if (IsXMMReversedOperands(opcode)) - spew("%-11s%s, .Lfrom%d%+d(%%rip)", legacySSEOpName(name), nameFPReg(dst), label.offset(), ripOffset); + spew("%-11s%s, .Lfrom%d%+d(%%rip)", legacySSEOpName(name), XMMRegName(dst), label.offset(), ripOffset); else - spew("%-11s.Lfrom%d%+d(%%rip), %s", legacySSEOpName(name), label.offset(), ripOffset, nameFPReg(dst)); + spew("%-11s.Lfrom%d%+d(%%rip), %s", legacySSEOpName(name), label.offset(), ripOffset, XMMRegName(dst)); return label; } @@ -4036,11 +4069,11 @@ private: JmpSrc label(m_formatter.size()); if (src0 == X86Registers::invalid_xmm) { if (IsXMMReversedOperands(opcode)) - spew("%-11s%s, .Lfrom%d%+d(%%rip)", name, nameFPReg(dst), label.offset(), ripOffset); + spew("%-11s%s, .Lfrom%d%+d(%%rip)", name, XMMRegName(dst), label.offset(), ripOffset); else - spew("%-11s.Lfrom%d%+d(%%rip), %s", name, label.offset(), ripOffset, nameFPReg(dst)); + spew("%-11s.Lfrom%d%+d(%%rip), %s", name, label.offset(), ripOffset, XMMRegName(dst)); } else { - spew("%-11s.Lfrom%d%+d(%%rip), %s, %s", name, label.offset(), ripOffset, nameFPReg(src0), nameFPReg(dst)); + spew("%-11s.Lfrom%d%+d(%%rip), %s, %s", name, label.offset(), ripOffset, XMMRegName(src0), XMMRegName(dst)); } return label; } @@ -4051,9 +4084,9 @@ private: { if (useLegacySSEEncoding(src0, dst)) { if (IsXMMReversedOperands(opcode)) - spew("%-11s%s, %s", legacySSEOpName(name), nameFPReg(dst), nameFPReg(rm)); + spew("%-11s%s, %s", legacySSEOpName(name), XMMRegName(dst), XMMRegName(rm)); else - spew("%-11s%s, %s", legacySSEOpName(name), nameFPReg(rm), nameFPReg(dst)); + spew("%-11s%s, %s", legacySSEOpName(name), XMMRegName(rm), XMMRegName(dst)); m_formatter.legacySSEPrefix(ty); m_formatter.twoByteOp(opcode, (RegisterID)rm, dst); return; @@ -4061,11 +4094,11 @@ private: if (src0 == X86Registers::invalid_xmm) { if (IsXMMReversedOperands(opcode)) - spew("%-11s%s, %s", name, nameFPReg(dst), nameFPReg(rm)); + spew("%-11s%s, %s", name, XMMRegName(dst), XMMRegName(rm)); else - spew("%-11s%s, %s", name, nameFPReg(rm), nameFPReg(dst)); + spew("%-11s%s, %s", name, XMMRegName(rm), XMMRegName(dst)); } else { - spew("%-11s%s, %s, %s", name, nameFPReg(rm), nameFPReg(src0), nameFPReg(dst)); + spew("%-11s%s, %s, %s", name, XMMRegName(rm), XMMRegName(src0), XMMRegName(dst)); } m_formatter.twoByteOpVex(ty, opcode, (RegisterID)rm, src0, dst); } @@ -4074,7 +4107,7 @@ private: uint32_t imm, XMMRegisterID rm, XMMRegisterID src0, XMMRegisterID dst) { if (useLegacySSEEncoding(src0, dst)) { - spew("%-11s$0x%x, %s, %s", legacySSEOpName(name), imm, nameFPReg(rm), nameFPReg(dst)); + spew("%-11s$0x%x, %s, %s", legacySSEOpName(name), imm, XMMRegName(rm), XMMRegName(dst)); m_formatter.legacySSEPrefix(ty); m_formatter.twoByteOp(opcode, (RegisterID)rm, dst); m_formatter.immediate8u(imm); @@ -4082,9 +4115,9 @@ private: } if (src0 == X86Registers::invalid_xmm) - spew("%-11s$0x%x, %s, %s", name, imm, nameFPReg(rm), nameFPReg(dst)); + spew("%-11s$0x%x, %s, %s", name, imm, XMMRegName(rm), XMMRegName(dst)); else - spew("%-11s$0x%x, %s, %s, %s", name, imm, nameFPReg(rm), nameFPReg(src0), nameFPReg(dst)); + spew("%-11s$0x%x, %s, %s, %s", name, imm, XMMRegName(rm), XMMRegName(src0), XMMRegName(dst)); m_formatter.twoByteOpVex(ty, opcode, (RegisterID)rm, src0, dst); m_formatter.immediate8u(imm); } @@ -4095,10 +4128,10 @@ private: if (useLegacySSEEncoding(src0, dst)) { if (IsXMMReversedOperands(opcode)) { spew("%-11s%s, " MEM_ob, legacySSEOpName(name), - nameFPReg(dst), ADDR_ob(offset, base)); + XMMRegName(dst), ADDR_ob(offset, base)); } else { spew("%-11s" MEM_ob ", %s", legacySSEOpName(name), - ADDR_ob(offset, base), nameFPReg(dst)); + ADDR_ob(offset, base), XMMRegName(dst)); } m_formatter.legacySSEPrefix(ty); m_formatter.twoByteOp(opcode, offset, base, dst); @@ -4107,12 +4140,12 @@ private: if (src0 == X86Registers::invalid_xmm) { if (IsXMMReversedOperands(opcode)) - spew("%-11s%s, " MEM_ob, name, nameFPReg(dst), ADDR_ob(offset, base)); + spew("%-11s%s, " MEM_ob, name, XMMRegName(dst), ADDR_ob(offset, base)); else - spew("%-11s" MEM_ob ", %s", name, ADDR_ob(offset, base), nameFPReg(dst)); + spew("%-11s" MEM_ob ", %s", name, ADDR_ob(offset, base), XMMRegName(dst)); } else { spew("%-11s" MEM_ob ", %s, %s", name, - ADDR_ob(offset, base), nameFPReg(src0), nameFPReg(dst)); + ADDR_ob(offset, base), XMMRegName(src0), XMMRegName(dst)); } m_formatter.twoByteOpVex(ty, opcode, offset, base, src0, dst); } @@ -4122,9 +4155,9 @@ private: { if (useLegacySSEEncoding(src0, dst)) { if (IsXMMReversedOperands(opcode)) - spew("%-11s%s, " MEM_o32b, legacySSEOpName(name), nameFPReg(dst), ADDR_o32b(offset, base)); + spew("%-11s%s, " MEM_o32b, legacySSEOpName(name), XMMRegName(dst), ADDR_o32b(offset, base)); else - spew("%-11s" MEM_o32b ", %s", legacySSEOpName(name), ADDR_o32b(offset, base), nameFPReg(dst)); + spew("%-11s" MEM_o32b ", %s", legacySSEOpName(name), ADDR_o32b(offset, base), XMMRegName(dst)); m_formatter.legacySSEPrefix(ty); m_formatter.twoByteOp_disp32(opcode, offset, base, dst); return; @@ -4132,12 +4165,12 @@ private: if (src0 == X86Registers::invalid_xmm) { if (IsXMMReversedOperands(opcode)) - spew("%-11s%s, " MEM_o32b, name, nameFPReg(dst), ADDR_o32b(offset, base)); + spew("%-11s%s, " MEM_o32b, name, XMMRegName(dst), ADDR_o32b(offset, base)); else - spew("%-11s" MEM_o32b ", %s", name, ADDR_o32b(offset, base), nameFPReg(dst)); + spew("%-11s" MEM_o32b ", %s", name, ADDR_o32b(offset, base), XMMRegName(dst)); } else { spew("%-11s" MEM_o32b ", %s, %s", name, - ADDR_o32b(offset, base), nameFPReg(src0), nameFPReg(dst)); + ADDR_o32b(offset, base), XMMRegName(src0), XMMRegName(dst)); } m_formatter.twoByteOpVex_disp32(ty, opcode, offset, base, src0, dst); } @@ -4147,7 +4180,7 @@ private: { if (useLegacySSEEncoding(src0, dst)) { spew("%-11s$0x%x, " MEM_ob ", %s", legacySSEOpName(name), imm, - ADDR_ob(offset, base), nameFPReg(dst)); + ADDR_ob(offset, base), XMMRegName(dst)); m_formatter.legacySSEPrefix(ty); m_formatter.twoByteOp(opcode, offset, base, dst); m_formatter.immediate8u(imm); @@ -4155,7 +4188,7 @@ private: } spew("%-11s$0x%x, " MEM_ob ", %s, %s", name, imm, ADDR_ob(offset, base), - nameFPReg(src0), nameFPReg(dst)); + XMMRegName(src0), XMMRegName(dst)); m_formatter.twoByteOpVex(ty, opcode, offset, base, src0, dst); m_formatter.immediate8u(imm); } @@ -4167,10 +4200,10 @@ private: if (useLegacySSEEncoding(src0, dst)) { if (IsXMMReversedOperands(opcode)) { spew("%-11s%s, " MEM_obs, legacySSEOpName(name), - nameFPReg(dst), ADDR_obs(offset, base, index, scale)); + XMMRegName(dst), ADDR_obs(offset, base, index, scale)); } else { spew("%-11s" MEM_obs ", %s", legacySSEOpName(name), - ADDR_obs(offset, base, index, scale), nameFPReg(dst)); + ADDR_obs(offset, base, index, scale), XMMRegName(dst)); } m_formatter.legacySSEPrefix(ty); m_formatter.twoByteOp(opcode, offset, base, index, scale, dst); @@ -4179,15 +4212,15 @@ private: if (src0 == X86Registers::invalid_xmm) { if (IsXMMReversedOperands(opcode)) { - spew("%-11s%s, " MEM_obs, name, nameFPReg(dst), + spew("%-11s%s, " MEM_obs, name, XMMRegName(dst), ADDR_obs(offset, base, index, scale)); } else { spew("%-11s" MEM_obs ", %s", name, ADDR_obs(offset, base, index, scale), - nameFPReg(dst)); + XMMRegName(dst)); } } else { spew("%-11s" MEM_obs ", %s, %s", name, ADDR_obs(offset, base, index, scale), - nameFPReg(src0), nameFPReg(dst)); + XMMRegName(src0), XMMRegName(dst)); } m_formatter.twoByteOpVex(ty, opcode, offset, base, index, scale, src0, dst); } @@ -4197,9 +4230,9 @@ private: { if (useLegacySSEEncoding(src0, dst)) { if (IsXMMReversedOperands(opcode)) - spew("%-11s%s, %p", legacySSEOpName(name), nameFPReg(dst), address); + spew("%-11s%s, %p", legacySSEOpName(name), XMMRegName(dst), address); else - spew("%-11s%p, %s", legacySSEOpName(name), address, nameFPReg(dst)); + spew("%-11s%p, %s", legacySSEOpName(name), address, XMMRegName(dst)); m_formatter.legacySSEPrefix(ty); m_formatter.twoByteOp(opcode, address, dst); return; @@ -4207,11 +4240,11 @@ private: if (src0 == X86Registers::invalid_xmm) { if (IsXMMReversedOperands(opcode)) - spew("%-11s%s, %p", name, nameFPReg(dst), address); + spew("%-11s%s, %p", name, XMMRegName(dst), address); else - spew("%-11s%p, %s", name, address, nameFPReg(dst)); + spew("%-11s%p, %s", name, address, XMMRegName(dst)); } else { - spew("%-11s%p, %s, %s", name, address, nameFPReg(src0), nameFPReg(dst)); + spew("%-11s%p, %s, %s", name, address, XMMRegName(src0), XMMRegName(dst)); } m_formatter.twoByteOpVex(ty, opcode, address, src0, dst); } @@ -4220,14 +4253,14 @@ private: uint32_t imm, const void *address, XMMRegisterID src0, XMMRegisterID dst) { if (useLegacySSEEncoding(src0, dst)) { - spew("%-11s$0x%x, %p, %s", legacySSEOpName(name), imm, address, nameFPReg(dst)); + spew("%-11s$0x%x, %p, %s", legacySSEOpName(name), imm, address, XMMRegName(dst)); m_formatter.legacySSEPrefix(ty); m_formatter.twoByteOp(opcode, address, dst); m_formatter.immediate8u(imm); return; } - spew("%-11s$0x%x, %p, %s, %s", name, imm, address, nameFPReg(src0), nameFPReg(dst)); + spew("%-11s$0x%x, %p, %s, %s", name, imm, address, XMMRegName(src0), XMMRegName(dst)); m_formatter.twoByteOpVex(ty, opcode, address, src0, dst); m_formatter.immediate8u(imm); } @@ -4237,9 +4270,9 @@ private: { if (useLegacySSEEncoding(src0, dst)) { if (IsXMMReversedOperands(opcode)) - spew("%-11s%s, %s", legacySSEOpName(name), nameFPReg(dst), nameIReg(4, rm)); + spew("%-11s%s, %s", legacySSEOpName(name), XMMRegName(dst), GPReg32Name(rm)); else - spew("%-11s%s, %s", legacySSEOpName(name), nameIReg(4, rm), nameFPReg(dst)); + spew("%-11s%s, %s", legacySSEOpName(name), GPReg32Name(rm), XMMRegName(dst)); m_formatter.legacySSEPrefix(ty); m_formatter.twoByteOp(opcode, rm, dst); return; @@ -4247,11 +4280,11 @@ private: if (src0 == X86Registers::invalid_xmm) { if (IsXMMReversedOperands(opcode)) - spew("%-11s%s, %s", name, nameFPReg(dst), nameIReg(4, rm)); + spew("%-11s%s, %s", name, XMMRegName(dst), GPReg32Name(rm)); else - spew("%-11s%s, %s", name, nameIReg(4, rm), nameFPReg(dst)); + spew("%-11s%s, %s", name, GPReg32Name(rm), XMMRegName(dst)); } else { - spew("%-11s%s, %s, %s", name, nameIReg(4, rm), nameFPReg(src0), nameFPReg(dst)); + spew("%-11s%s, %s, %s", name, GPReg32Name(rm), XMMRegName(src0), XMMRegName(dst)); } m_formatter.twoByteOpVex(ty, opcode, rm, src0, dst); } @@ -4262,9 +4295,9 @@ private: { if (useLegacySSEEncoding(src0, dst)) { if (IsXMMReversedOperands(opcode)) - spew("%-11s%s, %s", legacySSEOpName(name), nameFPReg(dst), nameIReg(rm)); + spew("%-11s%s, %s", legacySSEOpName(name), XMMRegName(dst), GPRegName(rm)); else - spew("%-11s%s, %s", legacySSEOpName(name), nameIReg(rm), nameFPReg(dst)); + spew("%-11s%s, %s", legacySSEOpName(name), GPRegName(rm), XMMRegName(dst)); m_formatter.legacySSEPrefix(ty); m_formatter.twoByteOp64(opcode, rm, dst); return; @@ -4272,11 +4305,11 @@ private: if (src0 == X86Registers::invalid_xmm) { if (IsXMMReversedOperands(opcode)) - spew("%-11s%s, %s", name, nameFPReg(dst), nameIReg(rm)); + spew("%-11s%s, %s", name, XMMRegName(dst), GPRegName(rm)); else - spew("%-11s%s, %s", name, nameIReg(rm), nameFPReg(dst)); + spew("%-11s%s, %s", name, GPRegName(rm), XMMRegName(dst)); } else { - spew("%-11s%s, %s, %s", name, nameIReg(rm), nameFPReg(src0), nameFPReg(dst)); + spew("%-11s%s, %s, %s", name, GPRegName(rm), XMMRegName(src0), XMMRegName(dst)); } m_formatter.twoByteOpVex64(ty, opcode, rm, src0, dst); } @@ -4287,22 +4320,22 @@ private: { if (useLegacySSEEncodingForOtherOutput()) { if (IsXMMReversedOperands(opcode)) - spew("%-11s%s, %s", legacySSEOpName(name), nameIReg(4, dst), nameFPReg(rm)); + spew("%-11s%s, %s", legacySSEOpName(name), GPReg32Name(dst), XMMRegName(rm)); else if (opcode == OP2_MOVD_EdVd) - spew("%-11s%s, %s", legacySSEOpName(name), nameFPReg((XMMRegisterID)dst), nameIReg(4, (RegisterID)rm)); + spew("%-11s%s, %s", legacySSEOpName(name), XMMRegName((XMMRegisterID)dst), GPReg32Name((RegisterID)rm)); else - spew("%-11s%s, %s", legacySSEOpName(name), nameFPReg(rm), nameIReg(4, dst)); + spew("%-11s%s, %s", legacySSEOpName(name), XMMRegName(rm), GPReg32Name(dst)); m_formatter.legacySSEPrefix(ty); m_formatter.twoByteOp(opcode, (RegisterID)rm, dst); return; } if (IsXMMReversedOperands(opcode)) - spew("%-11s%s, %s", name, nameIReg(4, dst), nameFPReg(rm)); + spew("%-11s%s, %s", name, GPReg32Name(dst), XMMRegName(rm)); else if (opcode == OP2_MOVD_EdVd) - spew("%-11s%s, %s", name, nameFPReg((XMMRegisterID)dst), nameIReg(4, (RegisterID)rm)); + spew("%-11s%s, %s", name, XMMRegName((XMMRegisterID)dst), GPReg32Name((RegisterID)rm)); else - spew("%-11s%s, %s", name, nameFPReg(rm), nameIReg(4, dst)); + spew("%-11s%s, %s", name, XMMRegName(rm), GPReg32Name(dst)); m_formatter.twoByteOpVex(ty, opcode, (RegisterID)rm, X86Registers::invalid_xmm, dst); } @@ -4310,14 +4343,14 @@ private: uint32_t imm, XMMRegisterID rm, RegisterID dst) { if (useLegacySSEEncodingForOtherOutput()) { - spew("%-11s$0x%x, %s, %s", legacySSEOpName(name), imm, nameFPReg(rm), nameIReg(4, dst)); + spew("%-11s$0x%x, %s, %s", legacySSEOpName(name), imm, XMMRegName(rm), GPReg32Name(dst)); m_formatter.legacySSEPrefix(ty); m_formatter.twoByteOp(opcode, (RegisterID)rm, dst); m_formatter.immediate8u(imm); return; } - spew("%-11s$0x%x, %s, %s", name, imm, nameFPReg(rm), nameIReg(4, dst)); + spew("%-11s$0x%x, %s, %s", name, imm, XMMRegName(rm), GPReg32Name(dst)); m_formatter.twoByteOpVex(ty, opcode, (RegisterID)rm, X86Registers::invalid_xmm, dst); m_formatter.immediate8u(imm); } @@ -4328,22 +4361,22 @@ private: { if (useLegacySSEEncodingForOtherOutput()) { if (IsXMMReversedOperands(opcode)) - spew("%-11s%s, %s", legacySSEOpName(name), nameIReg(dst), nameFPReg(rm)); + spew("%-11s%s, %s", legacySSEOpName(name), GPRegName(dst), XMMRegName(rm)); else if (opcode == OP2_MOVD_EdVd) - spew("%-11s%s, %s", legacySSEOpName(name), nameFPReg((XMMRegisterID)dst), nameIReg((RegisterID)rm)); + spew("%-11s%s, %s", legacySSEOpName(name), XMMRegName((XMMRegisterID)dst), GPRegName((RegisterID)rm)); else - spew("%-11s%s, %s", legacySSEOpName(name), nameFPReg(rm), nameIReg(dst)); + spew("%-11s%s, %s", legacySSEOpName(name), XMMRegName(rm), GPRegName(dst)); m_formatter.legacySSEPrefix(ty); m_formatter.twoByteOp64(opcode, (RegisterID)rm, dst); return; } if (IsXMMReversedOperands(opcode)) - spew("%-11s%s, %s", name, nameIReg(dst), nameFPReg(rm)); + spew("%-11s%s, %s", name, GPRegName(dst), XMMRegName(rm)); else if (opcode == OP2_MOVD_EdVd) - spew("%-11s%s, %s", name, nameFPReg((XMMRegisterID)dst), nameIReg((RegisterID)rm)); + spew("%-11s%s, %s", name, XMMRegName((XMMRegisterID)dst), GPRegName((RegisterID)rm)); else - spew("%-11s%s, %s", name, nameFPReg(rm), nameIReg(dst)); + spew("%-11s%s, %s", name, XMMRegName(rm), GPRegName(dst)); m_formatter.twoByteOpVex64(ty, opcode, (RegisterID)rm, X86Registers::invalid_xmm, (XMMRegisterID)dst); } #endif @@ -4352,13 +4385,13 @@ private: XMMRegisterID rm, XMMRegisterID reg) { if (useLegacySSEEncodingForOtherOutput()) { - spew("%-11s%s, %s", legacySSEOpName(name), nameFPReg(rm), nameFPReg(reg)); + spew("%-11s%s, %s", legacySSEOpName(name), XMMRegName(rm), XMMRegName(reg)); m_formatter.legacySSEPrefix(ty); m_formatter.twoByteOp(opcode, (RegisterID)rm, reg); return; } - spew("%-11s%s, %s", name, nameFPReg(rm), nameFPReg(reg)); + spew("%-11s%s, %s", name, XMMRegName(rm), XMMRegName(reg)); m_formatter.twoByteOpVex(ty, opcode, (RegisterID)rm, X86Registers::invalid_xmm, (XMMRegisterID)reg); } @@ -4367,14 +4400,14 @@ private: { if (useLegacySSEEncodingForOtherOutput()) { spew("%-11s" MEM_ob ", %s", legacySSEOpName(name), - ADDR_ob(offset, base), nameFPReg(reg)); + ADDR_ob(offset, base), XMMRegName(reg)); m_formatter.legacySSEPrefix(ty); m_formatter.twoByteOp(opcode, offset, base, reg); return; } spew("%-11s" MEM_ob ", %s", name, - ADDR_ob(offset, base), nameFPReg(reg)); + ADDR_ob(offset, base), XMMRegName(reg)); m_formatter.twoByteOpVex(ty, opcode, offset, base, X86Registers::invalid_xmm, (XMMRegisterID)reg); } @@ -4383,13 +4416,13 @@ private: XMMRegisterID rm, XMMRegisterID src0, XMMRegisterID dst) { if (useLegacySSEEncoding(src0, dst)) { - spew("%-11s%s, %s", legacySSEOpName(name), nameFPReg(rm), nameFPReg(dst)); + spew("%-11s%s, %s", legacySSEOpName(name), XMMRegName(rm), XMMRegName(dst)); m_formatter.legacySSEPrefix(ty); m_formatter.threeByteOp(opcode, escape, (RegisterID)rm, dst); return; } - spew("%-11s%s, %s, %s", name, nameFPReg(rm), nameFPReg(src0), nameFPReg(dst)); + spew("%-11s%s, %s, %s", name, XMMRegName(rm), XMMRegName(src0), XMMRegName(dst)); m_formatter.threeByteOpVex(ty, opcode, escape, (RegisterID)rm, src0, dst); } @@ -4398,14 +4431,14 @@ private: uint32_t imm, XMMRegisterID rm, XMMRegisterID src0, XMMRegisterID dst) { if (useLegacySSEEncoding(src0, dst)) { - spew("%-11s$0x%x, %s, %s", legacySSEOpName(name), imm, nameFPReg(rm), nameFPReg(dst)); + spew("%-11s$0x%x, %s, %s", legacySSEOpName(name), imm, XMMRegName(rm), XMMRegName(dst)); m_formatter.legacySSEPrefix(ty); m_formatter.threeByteOp(opcode, escape, (RegisterID)rm, dst); m_formatter.immediate8u(imm); return; } - spew("%-11s$0x%x, %s, %s, %s", name, imm, nameFPReg(rm), nameFPReg(src0), nameFPReg(dst)); + spew("%-11s$0x%x, %s, %s, %s", name, imm, XMMRegName(rm), XMMRegName(src0), XMMRegName(dst)); m_formatter.threeByteOpVex(ty, opcode, escape, (RegisterID)rm, src0, dst); m_formatter.immediate8u(imm); } @@ -4416,14 +4449,14 @@ private: { if (useLegacySSEEncoding(src0, dst)) { spew("%-11s" MEM_ob ", %s", legacySSEOpName(name), - ADDR_ob(offset, base), nameFPReg(dst)); + ADDR_ob(offset, base), XMMRegName(dst)); m_formatter.legacySSEPrefix(ty); m_formatter.threeByteOp(opcode, escape, offset, base, dst); return; } spew("%-11s" MEM_ob ", %s, %s", name, - ADDR_ob(offset, base), nameFPReg(src0), nameFPReg(dst)); + ADDR_ob(offset, base), XMMRegName(src0), XMMRegName(dst)); m_formatter.threeByteOpVex(ty, opcode, escape, offset, base, src0, dst); } @@ -4433,7 +4466,7 @@ private: { if (useLegacySSEEncoding(src0, dst)) { spew("%-11s$0x%x, " MEM_ob ", %s", legacySSEOpName(name), imm, - ADDR_ob(offset, base), nameFPReg(dst)); + ADDR_ob(offset, base), XMMRegName(dst)); m_formatter.legacySSEPrefix(ty); m_formatter.threeByteOp(opcode, escape, offset, base, dst); m_formatter.immediate8u(imm); @@ -4441,7 +4474,7 @@ private: } spew("%-11s$0x%x, " MEM_ob ", %s, %s", name, imm, ADDR_ob(offset, base), - nameFPReg(src0), nameFPReg(dst)); + XMMRegName(src0), XMMRegName(dst)); m_formatter.threeByteOpVex(ty, opcode, escape, offset, base, src0, dst); m_formatter.immediate8u(imm); } @@ -4451,13 +4484,13 @@ private: const void *address, XMMRegisterID src0, XMMRegisterID dst) { if (useLegacySSEEncoding(src0, dst)) { - spew("%-11s%p, %s", legacySSEOpName(name), address, nameFPReg(dst)); + spew("%-11s%p, %s", legacySSEOpName(name), address, XMMRegName(dst)); m_formatter.legacySSEPrefix(ty); m_formatter.threeByteOp(opcode, escape, address, dst); return; } - spew("%-11s%p, %s, %s", name, address, nameFPReg(src0), nameFPReg(dst)); + spew("%-11s%p, %s, %s", name, address, XMMRegName(src0), XMMRegName(dst)); m_formatter.threeByteOpVex(ty, opcode, escape, address, src0, dst); } @@ -4466,14 +4499,14 @@ private: RegisterID src1, XMMRegisterID src0, XMMRegisterID dst) { if (useLegacySSEEncoding(src0, dst)) { - spew("%-11s$0x%x, %s, %s", legacySSEOpName(name), imm, nameIReg(4, src1), nameFPReg(dst)); + spew("%-11s$0x%x, %s, %s", legacySSEOpName(name), imm, GPReg32Name(src1), XMMRegName(dst)); m_formatter.legacySSEPrefix(ty); m_formatter.threeByteOp(opcode, escape, src1, dst); m_formatter.immediate8u(imm); return; } - spew("%-11s$0x%x, %s, %s, %s", name, imm, nameIReg(4, src1), nameFPReg(src0), nameFPReg(dst)); + spew("%-11s$0x%x, %s, %s, %s", name, imm, GPReg32Name(src1), XMMRegName(src0), XMMRegName(dst)); m_formatter.threeByteOpVex(ty, opcode, escape, src1, src0, dst); m_formatter.immediate8u(imm); } @@ -4483,14 +4516,14 @@ private: int32_t offset, RegisterID base, XMMRegisterID src0, XMMRegisterID dst) { if (useLegacySSEEncoding(src0, dst)) { - spew("%-11s$0x%x, " MEM_ob ", %s", legacySSEOpName(name), imm, ADDR_ob(offset, base), nameFPReg(dst)); + spew("%-11s$0x%x, " MEM_ob ", %s", legacySSEOpName(name), imm, ADDR_ob(offset, base), XMMRegName(dst)); m_formatter.legacySSEPrefix(ty); m_formatter.threeByteOp(opcode, escape, offset, base, dst); m_formatter.immediate8u(imm); return; } - spew("%-11s$0x%x, " MEM_ob ", %s, %s", name, imm, ADDR_ob(offset, base), nameFPReg(src0), nameFPReg(dst)); + spew("%-11s$0x%x, " MEM_ob ", %s, %s", name, imm, ADDR_ob(offset, base), XMMRegName(src0), XMMRegName(dst)); m_formatter.threeByteOpVex(ty, opcode, escape, offset, base, src0, dst); m_formatter.immediate8u(imm); } @@ -4500,7 +4533,7 @@ private: XMMRegisterID src, RegisterID dst) { if (useLegacySSEEncodingForOtherOutput()) { - spew("%-11s$0x%x, %s, %s", legacySSEOpName(name), imm, nameFPReg(src), nameIReg(4, dst)); + spew("%-11s$0x%x, %s, %s", legacySSEOpName(name), imm, XMMRegName(src), GPReg32Name(dst)); m_formatter.legacySSEPrefix(ty); m_formatter.threeByteOp(opcode, escape, (RegisterID)src, dst); m_formatter.immediate8u(imm); @@ -4508,9 +4541,9 @@ private: } if (opcode == OP3_PEXTRD_EdVdqIb) - spew("%-11s$0x%x, %s, %s", name, imm, nameFPReg((XMMRegisterID)dst), nameIReg(4, (RegisterID)src)); + spew("%-11s$0x%x, %s, %s", name, imm, XMMRegName((XMMRegisterID)dst), GPReg32Name((RegisterID)src)); else - spew("%-11s$0x%x, %s, %s", name, imm, nameFPReg(src), nameIReg(4, dst)); + spew("%-11s$0x%x, %s, %s", name, imm, XMMRegName(src), GPReg32Name(dst)); m_formatter.threeByteOpVex(ty, opcode, escape, (RegisterID)src, X86Registers::invalid_xmm, dst); m_formatter.immediate8u(imm); } @@ -4520,14 +4553,14 @@ private: int32_t offset, RegisterID base, RegisterID dst) { if (useLegacySSEEncodingForOtherOutput()) { - spew("%-11s$0x%x, " MEM_ob ", %s", legacySSEOpName(name), imm, ADDR_ob(offset, base), nameIReg(4, dst)); + spew("%-11s$0x%x, " MEM_ob ", %s", legacySSEOpName(name), imm, ADDR_ob(offset, base), GPReg32Name(dst)); m_formatter.legacySSEPrefix(ty); m_formatter.threeByteOp(opcode, escape, offset, base, dst); m_formatter.immediate8u(imm); return; } - spew("%-11s$0x%x, " MEM_ob ", %s", name, imm, ADDR_ob(offset, base), nameIReg(4, dst)); + spew("%-11s$0x%x, " MEM_ob ", %s", name, imm, ADDR_ob(offset, base), GPReg32Name(dst)); m_formatter.threeByteOpVex(ty, opcode, escape, offset, base, X86Registers::invalid_xmm, dst); m_formatter.immediate8u(imm); } @@ -4537,7 +4570,7 @@ private: void vblendvOpSimd(XMMRegisterID mask, XMMRegisterID rm, XMMRegisterID src0, XMMRegisterID dst) { if (useLegacySSEEncodingForVblendv(mask, src0, dst)) { - spew("blendvps %s, %s", nameFPReg(rm), nameFPReg(dst)); + spew("blendvps %s, %s", XMMRegName(rm), XMMRegName(dst)); // Even though a "ps" instruction, vblendv is encoded with the "pd" prefix. m_formatter.legacySSEPrefix(VEX_PD); m_formatter.threeByteOp(OP3_BLENDVPS_VdqWdq, ESCAPE_BLENDVPS, (RegisterID)rm, dst); @@ -4545,7 +4578,7 @@ private: } spew("vblendvps %s, %s, %s, %s", - nameFPReg(mask), nameFPReg(rm), nameFPReg(src0), nameFPReg(dst)); + XMMRegName(mask), XMMRegName(rm), XMMRegName(src0), XMMRegName(dst)); // Even though a "ps" instruction, vblendv is encoded with the "pd" prefix. m_formatter.vblendvOpVex(VEX_PD, OP3_VBLENDVPS_VdqWdq, ESCAPE_VBLENDVPS, mask, (RegisterID)rm, src0, dst); @@ -4554,7 +4587,7 @@ private: void vblendvOpSimd(XMMRegisterID mask, int32_t offset, RegisterID base, XMMRegisterID src0, XMMRegisterID dst) { if (useLegacySSEEncodingForVblendv(mask, src0, dst)) { - spew("blendvps " MEM_ob ", %s", ADDR_ob(offset, base), nameFPReg(dst)); + spew("blendvps " MEM_ob ", %s", ADDR_ob(offset, base), XMMRegName(dst)); // Even though a "ps" instruction, vblendv is encoded with the "pd" prefix. m_formatter.legacySSEPrefix(VEX_PD); m_formatter.threeByteOp(OP3_BLENDVPS_VdqWdq, ESCAPE_BLENDVPS, offset, base, dst); @@ -4562,7 +4595,7 @@ private: } spew("vblendvps %s, " MEM_ob ", %s, %s", - nameFPReg(mask), ADDR_ob(offset, base), nameFPReg(src0), nameFPReg(dst)); + XMMRegName(mask), ADDR_ob(offset, base), XMMRegName(src0), XMMRegName(dst)); // Even though a "ps" instruction, vblendv is encoded with the "pd" prefix. m_formatter.vblendvOpVex(VEX_PD, OP3_VBLENDVPS_VdqWdq, ESCAPE_VBLENDVPS, mask, offset, base, src0, dst); @@ -4572,14 +4605,14 @@ private: uint32_t imm, XMMRegisterID src, XMMRegisterID dst) { if (useLegacySSEEncoding(src, dst)) { - spew("%-11s$%d, %s", legacySSEOpName(name), imm, nameFPReg(dst)); + spew("%-11s$%d, %s", legacySSEOpName(name), imm, XMMRegName(dst)); m_formatter.legacySSEPrefix(VEX_PD); m_formatter.twoByteOp(opcode, (RegisterID)dst, (int)shiftKind); m_formatter.immediate8u(imm); return; } - spew("%-11s$%d, %s, %s", name, imm, nameFPReg(src), nameFPReg(dst)); + spew("%-11s$%d, %s, %s", name, imm, XMMRegName(src), XMMRegName(dst)); m_formatter.twoByteOpVex(VEX_PD, opcode, (RegisterID)dst, src, (int)shiftKind); m_formatter.immediate8u(imm); } @@ -5049,10 +5082,10 @@ private: void oneByteOp8(OneByteOpcodeID opcode, RegisterID rm, GroupOpcodeID groupOp) { #ifdef JS_CODEGEN_X86 - MOZ_ASSERT(!ByteRegRequiresRex(rm)); + MOZ_ASSERT(!X86Registers::ByteRegRequiresRex(rm)); #endif m_buffer.ensureSpace(maxInstructionSize); - emitRexIf(ByteRegRequiresRex(rm), 0, 0, rm); + emitRexIf(X86Registers::ByteRegRequiresRex(rm), 0, 0, rm); m_buffer.putByteUnchecked(opcode); registerModRM(rm, groupOp); } @@ -5066,46 +5099,48 @@ private: registerModRM(rm, groupOp); } - void oneByteOp8(OneByteOpcodeID opcode, int32_t offset, RegisterID base, int reg) + void oneByteOp8(OneByteOpcodeID opcode, int32_t offset, RegisterID base, RegisterID reg) { #ifdef JS_CODEGEN_X86 - MOZ_ASSERT(!ByteRegRequiresRex(reg)); + MOZ_ASSERT(!X86Registers::ByteRegRequiresRex(reg)); #endif m_buffer.ensureSpace(maxInstructionSize); - emitRexIf(ByteRegRequiresRex(reg), reg, 0, base); + emitRexIf(X86Registers::ByteRegRequiresRex(reg), reg, 0, base); m_buffer.putByteUnchecked(opcode); memoryModRM(offset, base, reg); } - void oneByteOp8_disp32(OneByteOpcodeID opcode, int32_t offset, RegisterID base, int reg) + void oneByteOp8_disp32(OneByteOpcodeID opcode, int32_t offset, RegisterID base, + RegisterID reg) { #ifdef JS_CODEGEN_X86 - MOZ_ASSERT(!ByteRegRequiresRex(reg)); + MOZ_ASSERT(!X86Registers::ByteRegRequiresRex(reg)); #endif m_buffer.ensureSpace(maxInstructionSize); - emitRexIf(ByteRegRequiresRex(reg), reg, 0, base); + emitRexIf(X86Registers::ByteRegRequiresRex(reg), reg, 0, base); m_buffer.putByteUnchecked(opcode); memoryModRM_disp32(offset, base, reg); } - void oneByteOp8(OneByteOpcodeID opcode, int32_t offset, RegisterID base, RegisterID index, int scale, int reg) + void oneByteOp8(OneByteOpcodeID opcode, int32_t offset, RegisterID base, + RegisterID index, int scale, RegisterID reg) { #ifdef JS_CODEGEN_X86 - MOZ_ASSERT(!ByteRegRequiresRex(reg)); + MOZ_ASSERT(!X86Registers::ByteRegRequiresRex(reg)); #endif m_buffer.ensureSpace(maxInstructionSize); - emitRexIf(ByteRegRequiresRex(reg), reg, index, base); + emitRexIf(X86Registers::ByteRegRequiresRex(reg), reg, index, base); m_buffer.putByteUnchecked(opcode); memoryModRM(offset, base, index, scale, reg); } - void oneByteOp8(OneByteOpcodeID opcode, const void* address, int reg) + void oneByteOp8(OneByteOpcodeID opcode, const void* address, RegisterID reg) { #ifdef JS_CODEGEN_X86 - MOZ_ASSERT(!ByteRegRequiresRex(reg)); + MOZ_ASSERT(!X86Registers::ByteRegRequiresRex(reg)); #endif m_buffer.ensureSpace(maxInstructionSize); - emitRexIf(ByteRegRequiresRex(reg), reg, 0, 0); + emitRexIf(X86Registers::ByteRegRequiresRex(reg), reg, 0, 0); m_buffer.putByteUnchecked(opcode); memoryModRM_disp32(address, reg); } @@ -5113,7 +5148,7 @@ private: void twoByteOp8(TwoByteOpcodeID opcode, RegisterID rm, RegisterID reg) { m_buffer.ensureSpace(maxInstructionSize); - emitRexIf(ByteRegRequiresRex(reg)|ByteRegRequiresRex(rm), reg, 0, rm); + emitRexIf(X86Registers::ByteRegRequiresRex(reg)|X86Registers::ByteRegRequiresRex(rm), reg, 0, rm); m_buffer.putByteUnchecked(OP_2BYTE_ESCAPE); m_buffer.putByteUnchecked(opcode); registerModRM(rm, reg); @@ -5126,7 +5161,7 @@ private: void twoByteOp8_movx(TwoByteOpcodeID opcode, RegisterID rm, RegisterID reg) { m_buffer.ensureSpace(maxInstructionSize); - emitRexIf(regRequiresRex(reg)|ByteRegRequiresRex(rm), reg, 0, rm); + emitRexIf(regRequiresRex(reg)|X86Registers::ByteRegRequiresRex(rm), reg, 0, rm); m_buffer.putByteUnchecked(OP_2BYTE_ESCAPE); m_buffer.putByteUnchecked(opcode); registerModRM(rm, reg); @@ -5135,7 +5170,7 @@ private: void twoByteOp8(TwoByteOpcodeID opcode, RegisterID rm, GroupOpcodeID groupOp) { m_buffer.ensureSpace(maxInstructionSize); - emitRexIf(ByteRegRequiresRex(rm), 0, 0, rm); + emitRexIf(X86Registers::ByteRegRequiresRex(rm), 0, 0, rm); m_buffer.putByteUnchecked(OP_2BYTE_ESCAPE); m_buffer.putByteUnchecked(opcode); registerModRM(rm, groupOp); From 089b6c76f48d2d402a21dc281adf796574f06fdd Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 30 Jan 2015 16:05:55 -0800 Subject: [PATCH 061/101] Bug 1125202 - SpiderMonkey: Replace AssemblerBuffer's internals with mozilla::Vector r=sstangl --- js/src/jit/shared/Assembler-x86-shared.cpp | 2 +- .../jit/shared/AssemblerBuffer-x86-shared.h | 149 +++++------------- js/src/jit/shared/BaseAssembler-x86-shared.h | 9 +- 3 files changed, 41 insertions(+), 119 deletions(-) diff --git a/js/src/jit/shared/Assembler-x86-shared.cpp b/js/src/jit/shared/Assembler-x86-shared.cpp index 26df39c32b9..7f3f1bbe8d7 100644 --- a/js/src/jit/shared/Assembler-x86-shared.cpp +++ b/js/src/jit/shared/Assembler-x86-shared.cpp @@ -91,7 +91,7 @@ AssemblerX86Shared::trace(JSTracer *trc) } if (dataRelocations_.length()) { CompactBufferReader reader(dataRelocations_); - ::TraceDataRelocations(trc, masm.buffer(), reader); + ::TraceDataRelocations(trc, masm.data(), reader); } } diff --git a/js/src/jit/shared/AssemblerBuffer-x86-shared.h b/js/src/jit/shared/AssemblerBuffer-x86-shared.h index dce187c9c02..40b982d2b06 100644 --- a/js/src/jit/shared/AssemblerBuffer-x86-shared.h +++ b/js/src/jit/shared/AssemblerBuffer-x86-shared.h @@ -75,96 +75,78 @@ namespace js { namespace jit { class AssemblerBuffer { - static const size_t inlineCapacity = 256; public: AssemblerBuffer() - : m_buffer(m_inlineBuffer) - , m_capacity(inlineCapacity) - , m_size(0) - , m_allocSize(0) - , m_oom(false) + : m_oom(false) { } - ~AssemblerBuffer() - { - if (m_buffer != m_inlineBuffer) - js_free(m_buffer); - } - void ensureSpace(size_t space) { - if (m_size > m_capacity - space) - grow(); + if (MOZ_UNLIKELY(!m_buffer.reserve(m_buffer.length() + space))) + oomDetected(); } bool isAligned(size_t alignment) const { - return !(m_size & (alignment - 1)); + return !(m_buffer.length() & (alignment - 1)); } void putByteUnchecked(int value) { - MOZ_ASSERT(!(m_size > m_capacity - 4)); - m_buffer[m_size] = char(value); - m_size++; + m_buffer.infallibleAppend(char(value)); } void putByte(int value) { - if (m_size > m_capacity - 4) - grow(); - putByteUnchecked(value); + if (MOZ_UNLIKELY(!m_buffer.append(char(value)))) + oomDetected(); } void putShortUnchecked(int value) { - MOZ_ASSERT(!(m_size > m_capacity - 4)); - *reinterpret_cast(&m_buffer[m_size]) = short(value); - m_size += 2; + m_buffer.infallibleGrowByUninitialized(2); + memcpy(m_buffer.end() - 2, &value, 2); } void putShort(int value) { - if (m_size > m_capacity - 4) - grow(); - putShortUnchecked(value); + if (MOZ_UNLIKELY(!m_buffer.growByUninitialized(2))) { + oomDetected(); + return; + } + memcpy(m_buffer.end() - 2, &value, 2); } void putIntUnchecked(int value) { - MOZ_ASSERT(!(m_size > m_capacity - 4)); - *reinterpret_cast(&m_buffer[m_size]) = value; - m_size += 4; + m_buffer.infallibleGrowByUninitialized(4); + memcpy(m_buffer.end() - 4, &value, 4); } void putInt64Unchecked(int64_t value) { - MOZ_ASSERT(!(m_size > m_capacity - 8)); - *reinterpret_cast(&m_buffer[m_size]) = value; - m_size += 8; + m_buffer.infallibleGrowByUninitialized(8); + memcpy(m_buffer.end() - 8, &value, 8); } void putInt(int value) { - if (m_size > m_capacity - 4) - grow(); - putIntUnchecked(value); + if (MOZ_UNLIKELY(!m_buffer.growByUninitialized(4))) { + oomDetected(); + return; + } + memcpy(m_buffer.end() - 4, &value, 4); } - void* data() const + unsigned char *data() { - return m_buffer; + return m_buffer.begin(); } size_t size() const { - return m_size; - } - - size_t allocSize() const - { - return m_allocSize; + return m_buffer.length(); } bool oom() const @@ -172,91 +154,32 @@ namespace jit { return m_oom; } - unsigned char *buffer() const { + const unsigned char *buffer() const { MOZ_ASSERT(!m_oom); - return reinterpret_cast(m_buffer); + return m_buffer.begin(); } protected: - void append(const char* data, size_t size) - { - if (m_size > m_capacity - size) - grow(size); - - // If we OOM and size > inlineCapacity, this would crash. - if (m_oom) - return; - memcpy(m_buffer + m_size, data, size); - m_size += size; - } - /* - * OOM handling: This class can OOM in the grow() method trying to - * allocate a new buffer. In response to an OOM, we need to avoid + * OOM handling: This class can OOM in the ensureSpace() method trying + * to allocate a new buffer. In response to an OOM, we need to avoid * crashing and report the error. We also want to make it so that * users of this class need to check for OOM only at certain points * and not after every operation. * - * Our strategy for handling an OOM is to set m_oom, and then set - * m_size to 0, preserving the current buffer. This way, the user + * Our strategy for handling an OOM is to set m_oom, and then clear (but + * not free) m_buffer, preserving the current buffer. This way, the user * can continue assembling into the buffer, deferring OOM checking * until the user wants to read code out of the buffer. * * See also the |buffer| method. */ - - void grow(size_t extraCapacity = 0) - { - char* newBuffer; - - /* - * If |extraCapacity| is zero (as it almost always is) this is an - * allocator-friendly doubling growth strategy. - */ - size_t doubleCapacity = m_capacity + m_capacity; - - // Check for overflow. - if (doubleCapacity < m_capacity) { - m_size = 0; - m_oom = true; - return; - } - - size_t newCapacity = doubleCapacity + extraCapacity; - - // Check for overflow. - if (newCapacity < doubleCapacity) { - m_size = 0; - m_oom = true; - return; - } - - if (m_buffer == m_inlineBuffer) { - newBuffer = static_cast(js_malloc(newCapacity)); - if (!newBuffer) { - m_size = 0; - m_oom = true; - return; - } - memcpy(newBuffer, m_buffer, m_size); - } else { - newBuffer = static_cast(js_realloc(m_buffer, newCapacity)); - if (!newBuffer) { - m_size = 0; - m_oom = true; - return; - } - } - - m_buffer = newBuffer; - m_capacity = newCapacity; + void oomDetected() { + m_oom = true; + m_buffer.clear(); } - char m_inlineBuffer[inlineCapacity]; - char* m_buffer; - size_t m_capacity; - size_t m_size; - size_t m_allocSize; + mozilla::Vector m_buffer; bool m_oom; }; diff --git a/js/src/jit/shared/BaseAssembler-x86-shared.h b/js/src/jit/shared/BaseAssembler-x86-shared.h index 957b7dc36a1..722d09cd7da 100644 --- a/js/src/jit/shared/BaseAssembler-x86-shared.h +++ b/js/src/jit/shared/BaseAssembler-x86-shared.h @@ -627,8 +627,8 @@ public: }; size_t size() const { return m_formatter.size(); } - size_t allocSize() const { return m_formatter.allocSize(); } - unsigned char *buffer() const { return m_formatter.buffer(); } + const unsigned char *buffer() const { return m_formatter.buffer(); } + unsigned char *data() { return m_formatter.data(); } bool oom() const { return m_formatter.oom(); } void nop() @@ -5292,11 +5292,10 @@ private: // Administrative methods: size_t size() const { return m_buffer.size(); } - size_t allocSize() const { return m_buffer.allocSize(); } - unsigned char *buffer() const { return m_buffer.buffer(); } + const unsigned char *buffer() const { return m_buffer.buffer(); } bool oom() const { return m_buffer.oom(); } bool isAligned(int alignment) const { return m_buffer.isAligned(alignment); } - void* data() const { return m_buffer.data(); } + unsigned char *data() { return m_buffer.data(); } private: From 8a8d52f6b3388737e85862f5118c5a01c65eb0fe Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 30 Jan 2015 16:05:55 -0800 Subject: [PATCH 062/101] Bug 1125202 - SpiderMonkey: Refactor a repeated assert so that it's checked in only one place r=jandem --- js/src/jit/shared/BaseAssembler-x86-shared.h | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/js/src/jit/shared/BaseAssembler-x86-shared.h b/js/src/jit/shared/BaseAssembler-x86-shared.h index 722d09cd7da..c4ac5cfe34e 100644 --- a/js/src/jit/shared/BaseAssembler-x86-shared.h +++ b/js/src/jit/shared/BaseAssembler-x86-shared.h @@ -5081,9 +5081,6 @@ private: void oneByteOp8(OneByteOpcodeID opcode, RegisterID rm, GroupOpcodeID groupOp) { -#ifdef JS_CODEGEN_X86 - MOZ_ASSERT(!X86Registers::ByteRegRequiresRex(rm)); -#endif m_buffer.ensureSpace(maxInstructionSize); emitRexIf(X86Registers::ByteRegRequiresRex(rm), 0, 0, rm); m_buffer.putByteUnchecked(opcode); @@ -5101,9 +5098,6 @@ private: void oneByteOp8(OneByteOpcodeID opcode, int32_t offset, RegisterID base, RegisterID reg) { -#ifdef JS_CODEGEN_X86 - MOZ_ASSERT(!X86Registers::ByteRegRequiresRex(reg)); -#endif m_buffer.ensureSpace(maxInstructionSize); emitRexIf(X86Registers::ByteRegRequiresRex(reg), reg, 0, base); m_buffer.putByteUnchecked(opcode); @@ -5113,9 +5107,6 @@ private: void oneByteOp8_disp32(OneByteOpcodeID opcode, int32_t offset, RegisterID base, RegisterID reg) { -#ifdef JS_CODEGEN_X86 - MOZ_ASSERT(!X86Registers::ByteRegRequiresRex(reg)); -#endif m_buffer.ensureSpace(maxInstructionSize); emitRexIf(X86Registers::ByteRegRequiresRex(reg), reg, 0, base); m_buffer.putByteUnchecked(opcode); @@ -5125,9 +5116,6 @@ private: void oneByteOp8(OneByteOpcodeID opcode, int32_t offset, RegisterID base, RegisterID index, int scale, RegisterID reg) { -#ifdef JS_CODEGEN_X86 - MOZ_ASSERT(!X86Registers::ByteRegRequiresRex(reg)); -#endif m_buffer.ensureSpace(maxInstructionSize); emitRexIf(X86Registers::ByteRegRequiresRex(reg), reg, index, base); m_buffer.putByteUnchecked(opcode); @@ -5136,9 +5124,6 @@ private: void oneByteOp8(OneByteOpcodeID opcode, const void* address, RegisterID reg) { -#ifdef JS_CODEGEN_X86 - MOZ_ASSERT(!X86Registers::ByteRegRequiresRex(reg)); -#endif m_buffer.ensureSpace(maxInstructionSize); emitRexIf(X86Registers::ByteRegRequiresRex(reg), reg, 0, 0); m_buffer.putByteUnchecked(opcode); @@ -5348,7 +5333,10 @@ private: #else // No REX prefix bytes on 32-bit x86. inline bool regRequiresRex(int) { return false; } - inline void emitRexIf(bool, int, int, int) {} + inline void emitRexIf(bool condition, int, int, int) + { + MOZ_ASSERT(!condition, "32-bit x86 should never use a REX prefix"); + } inline void emitRexIfNeeded(int, int, int) {} #endif From ae32a02593864033932c4c7c8bd6fc69855c4762 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 30 Jan 2015 16:05:55 -0800 Subject: [PATCH 063/101] Bug 1125202 - SpiderMonkey: Remove redundant inline keywords r=jandem --- js/src/jit/shared/BaseAssembler-x86-shared.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/js/src/jit/shared/BaseAssembler-x86-shared.h b/js/src/jit/shared/BaseAssembler-x86-shared.h index c4ac5cfe34e..000ec13870a 100644 --- a/js/src/jit/shared/BaseAssembler-x86-shared.h +++ b/js/src/jit/shared/BaseAssembler-x86-shared.h @@ -470,7 +470,7 @@ private: }; // Test whether the given opcode should be printed with its operands reversed. - static inline bool IsXMMReversedOperands(TwoByteOpcodeID opcode) { + static bool IsXMMReversedOperands(TwoByteOpcodeID opcode) { switch (opcode) { case OP2_MOVSD_WsdVsd: // also OP2_MOVPS_WpsVps case OP2_MOVAPS_WsdVsd: @@ -5294,19 +5294,19 @@ private: static const RegisterID hasSib2 = X86Registers::r12; // Registers r8 & above require a REX prefixe. - inline bool regRequiresRex(int reg) + bool regRequiresRex(int reg) { return (reg >= X86Registers::r8); } // Format a REX prefix byte. - inline void emitRex(bool w, int r, int x, int b) + void emitRex(bool w, int r, int x, int b) { m_buffer.putByteUnchecked(PRE_REX | ((int)w << 3) | ((r>>3)<<2) | ((x>>3)<<1) | (b>>3)); } // Used to plant a REX byte with REX.w set (for 64-bit operations). - inline void emitRexW(int r, int x, int b) + void emitRexW(int r, int x, int b) { emitRex(true, r, x, b); } @@ -5318,7 +5318,7 @@ private: // NB: WebKit's use of emitRexIf() is limited such that the // reqRequiresRex() checks are not needed. SpiderMonkey extends // oneByteOp8 functionality such that r, x, and b can all be used. - inline void emitRexIf(bool condition, int r, int x, int b) + void emitRexIf(bool condition, int r, int x, int b) { if (condition || regRequiresRex(r) || regRequiresRex(x) || regRequiresRex(b)) emitRex(false, r, x, b); @@ -5326,18 +5326,18 @@ private: // Used for word sized operations, will plant a REX prefix if necessary // (if any register is r8 or above). - inline void emitRexIfNeeded(int r, int x, int b) + void emitRexIfNeeded(int r, int x, int b) { emitRexIf(regRequiresRex(r) || regRequiresRex(x) || regRequiresRex(b), r, x, b); } #else // No REX prefix bytes on 32-bit x86. - inline bool regRequiresRex(int) { return false; } - inline void emitRexIf(bool condition, int, int, int) + bool regRequiresRex(int) { return false; } + void emitRexIf(bool condition, int, int, int) { MOZ_ASSERT(!condition, "32-bit x86 should never use a REX prefix"); } - inline void emitRexIfNeeded(int, int, int) {} + void emitRexIfNeeded(int, int, int) {} #endif enum ModRmMode { From 97f042949625e7b6530f72a79ca39ac0edeedb6f Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 30 Jan 2015 16:05:55 -0800 Subject: [PATCH 064/101] Bug 1125202 - SpiderMonkey: Reduce usage of reinterpret_cast r=jandem --- js/src/jit/shared/Assembler-x86-shared.cpp | 2 +- js/src/jit/shared/BaseAssembler-x86-shared.h | 28 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/js/src/jit/shared/Assembler-x86-shared.cpp b/js/src/jit/shared/Assembler-x86-shared.cpp index 7f3f1bbe8d7..9908ed1084c 100644 --- a/js/src/jit/shared/Assembler-x86-shared.cpp +++ b/js/src/jit/shared/Assembler-x86-shared.cpp @@ -67,7 +67,7 @@ TraceDataRelocations(JSTracer *trc, uint8_t *buffer, CompactBufferReader &reader #endif // No barrier needed since these are constants. - gc::MarkGCThingUnbarriered(trc, reinterpret_cast(ptr), "ion-masm-ptr"); + gc::MarkGCThingUnbarriered(trc, ptr, "ion-masm-ptr"); } } diff --git a/js/src/jit/shared/BaseAssembler-x86-shared.h b/js/src/jit/shared/BaseAssembler-x86-shared.h index 000ec13870a..4bd4133f5a5 100644 --- a/js/src/jit/shared/BaseAssembler-x86-shared.h +++ b/js/src/jit/shared/BaseAssembler-x86-shared.h @@ -2146,7 +2146,7 @@ public: #ifdef JS_CODEGEN_X64 m_formatter.immediate64(reinterpret_cast(addr)); #else - m_formatter.immediate32(reinterpret_cast(addr)); + m_formatter.immediate32(reinterpret_cast(addr)); #endif } @@ -2279,7 +2279,7 @@ public: #ifdef JS_CODEGEN_X64 m_formatter.immediate64(reinterpret_cast(addr)); #else - m_formatter.immediate32(reinterpret_cast(addr)); + m_formatter.immediate32(reinterpret_cast(addr)); #endif } @@ -3897,7 +3897,7 @@ threeByteOpImmSimd("vblendps", VEX_PD, OP3_BLENDPS_VpsWpsIb, ESCAPE_BLENDPS, imm if (oom()) return false; - char* code = reinterpret_cast(m_formatter.data()); + const unsigned char* code = m_formatter.data(); int32_t offset = getInt32(code + from.m_offset); if (offset == -1) return false; @@ -3911,7 +3911,7 @@ threeByteOpImmSimd("vblendps", VEX_PD, OP3_BLENDPS_VpsWpsIb, ESCAPE_BLENDPS, imm if (oom()) return; - char* code = reinterpret_cast(m_formatter.data()); + unsigned char* code = m_formatter.data(); setInt32(code + from.m_offset, to.m_offset); } @@ -3926,13 +3926,13 @@ threeByteOpImmSimd("vblendps", VEX_PD, OP3_BLENDPS_VpsWpsIb, ESCAPE_BLENDPS, imm return; spew("##link ((%d)) jumps to ((%d))", from.m_offset, to.m_offset); - char* code = reinterpret_cast(m_formatter.data()); + unsigned char* code = m_formatter.data(); setRel32(code + from.m_offset, code + to.m_offset); } static bool canRelinkJump(void* from, void* to) { - intptr_t offset = reinterpret_cast(to) - reinterpret_cast(from); + intptr_t offset = static_cast(to) - static_cast(from); return (offset == static_cast(offset)); } @@ -3943,7 +3943,7 @@ threeByteOpImmSimd("vblendps", VEX_PD, OP3_BLENDPS_VpsWpsIb, ESCAPE_BLENDPS, imm static void setRel32(void* from, void* to) { - intptr_t offset = reinterpret_cast(to) - reinterpret_cast(from); + intptr_t offset = static_cast(to) - static_cast(from); MOZ_ASSERT(offset == static_cast(offset), "offset is too great for a 32-bit relocation"); if (offset != static_cast(offset)) @@ -3959,20 +3959,20 @@ threeByteOpImmSimd("vblendps", VEX_PD, OP3_BLENDPS_VpsWpsIb, ESCAPE_BLENDPS, imm return (char *)where + rel; } - static void *getPointer(void* where) + static void *getPointer(const void* where) { - return reinterpret_cast(where)[-1]; + return static_cast(where)[-1]; } static void **getPointerRef(void* where) { - return &reinterpret_cast(where)[-1]; + return &static_cast(where)[-1]; } static void setPointer(void* where, const void* value) { staticSpew("##setPtr ((where=%p)) ((value=%p))", where, value); - reinterpret_cast(where)[-1] = value; + static_cast(where)[-1] = value; } // Test whether the given address will fit in an address immediate field. @@ -3997,7 +3997,7 @@ threeByteOpImmSimd("vblendps", VEX_PD, OP3_BLENDPS_VpsWpsIb, ESCAPE_BLENDPS, imm static void setInt32(void* where, int32_t value) { - reinterpret_cast(where)[-1] = value; + static_cast(where)[-1] = value; } private: @@ -4617,9 +4617,9 @@ private: m_formatter.immediate8u(imm); } - static int32_t getInt32(void* where) + static int32_t getInt32(const void* where) { - return reinterpret_cast(where)[-1]; + return static_cast(where)[-1]; } class X86InstructionFormatter { From 791213b7df7f7d020951cd8abf0f8d759f4719cd Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 30 Jan 2015 16:05:55 -0800 Subject: [PATCH 065/101] Bug 1125202 - SpiderMonkey: Eliminate unnecessary friend declarations r=jandem --- js/src/jit/shared/BaseAssembler-x86-shared.h | 29 ++++++++------------ 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/js/src/jit/shared/BaseAssembler-x86-shared.h b/js/src/jit/shared/BaseAssembler-x86-shared.h index 4bd4133f5a5..0a596a2266d 100644 --- a/js/src/jit/shared/BaseAssembler-x86-shared.h +++ b/js/src/jit/shared/BaseAssembler-x86-shared.h @@ -564,7 +564,6 @@ private: GROUP11_MOV = 0 }; - class X86InstructionFormatter; public: X86Assembler() : useVEX_(true) @@ -573,8 +572,6 @@ public: void disableVEX() { useVEX_ = false; } class JmpSrc { - friend class X86Assembler; - friend class X86InstructionFormatter; public: JmpSrc() : m_offset(-1) @@ -599,8 +596,6 @@ public: }; class JmpDst { - friend class X86Assembler; - friend class X86InstructionFormatter; public: JmpDst() : m_offset(-1) @@ -2663,7 +2658,7 @@ public: { m_formatter.oneByteOp(OP_CALL_rel32); JmpSrc r = m_formatter.immediateRel32(); - spew("call .Lfrom%d", r.m_offset); + spew("call .Lfrom%d", r.offset()); return r; } @@ -2687,7 +2682,7 @@ public: { m_formatter.oneByteOp(OP_CMP_EAXIv); JmpSrc r = m_formatter.immediateRel32(); - spew("cmpl %%eax, .Lfrom%d", r.m_offset); + spew("cmpl %%eax, .Lfrom%d", r.offset()); return r; } @@ -2712,7 +2707,7 @@ public: { m_formatter.oneByteOp(OP_JMP_rel32); JmpSrc r = m_formatter.immediateRel32(); - spew("jmp .Lfrom%d", r.m_offset); + spew("jmp .Lfrom%d", r.offset()); return r; } @@ -2769,7 +2764,7 @@ public: { m_formatter.twoByteOp(jccRel32(cond)); JmpSrc r = m_formatter.immediateRel32(); - spew("j%s .Lfrom%d", nameCC(cond), r.m_offset); + spew("j%s .Lfrom%d", nameCC(cond), r.offset()); return r; } @@ -3820,7 +3815,7 @@ threeByteOpImmSimd("vblendps", VEX_PD, OP3_BLENDPS_VpsWpsIb, ESCAPE_BLENDPS, imm JmpDst label() { JmpDst r = JmpDst(m_formatter.size()); - spew(".set .Llabel%d, .", r.m_offset); + spew(".set .Llabel%d, .", r.offset()); return r; } @@ -3830,7 +3825,7 @@ threeByteOpImmSimd("vblendps", VEX_PD, OP3_BLENDPS_VpsWpsIb, ESCAPE_BLENDPS, imm static JmpDst labelFor(JmpSrc jump, intptr_t offset = 0) { - return JmpDst(jump.m_offset + offset); + return JmpDst(jump.offset() + offset); } JmpDst align(int alignment) @@ -3898,7 +3893,7 @@ threeByteOpImmSimd("vblendps", VEX_PD, OP3_BLENDPS_VpsWpsIb, ESCAPE_BLENDPS, imm return false; const unsigned char* code = m_formatter.data(); - int32_t offset = getInt32(code + from.m_offset); + int32_t offset = getInt32(code + from.offset()); if (offset == -1) return false; *next = JmpSrc(offset); @@ -3912,22 +3907,22 @@ threeByteOpImmSimd("vblendps", VEX_PD, OP3_BLENDPS_VpsWpsIb, ESCAPE_BLENDPS, imm return; unsigned char* code = m_formatter.data(); - setInt32(code + from.m_offset, to.m_offset); + setInt32(code + from.offset(), to.offset()); } void linkJump(JmpSrc from, JmpDst to) { - MOZ_ASSERT(from.m_offset != -1); - MOZ_ASSERT(to.m_offset != -1); + MOZ_ASSERT(from.offset() != -1); + MOZ_ASSERT(to.offset() != -1); // Sanity check - if the assembler has OOM'd, it will start overwriting // its internal buffer and thus our links could be garbage. if (oom()) return; - spew("##link ((%d)) jumps to ((%d))", from.m_offset, to.m_offset); + spew("##link ((%d)) jumps to ((%d))", from.offset(), to.offset()); unsigned char* code = m_formatter.data(); - setRel32(code + from.m_offset, code + to.m_offset); + setRel32(code + from.offset(), code + to.offset()); } static bool canRelinkJump(void* from, void* to) From 8baf860af5973701eef452f9400b511f2edc5eee Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 30 Jan 2015 16:05:55 -0800 Subject: [PATCH 066/101] Bug 1125202 - SpiderMonkey: Implement all remaining FIXME_INSN_PRINTING uses r=bbouvier --- .../jit/shared/AssemblerBuffer-x86-shared.h | 6 -- js/src/jit/shared/BaseAssembler-x86-shared.h | 91 +++++++++---------- 2 files changed, 42 insertions(+), 55 deletions(-) diff --git a/js/src/jit/shared/AssemblerBuffer-x86-shared.h b/js/src/jit/shared/AssemblerBuffer-x86-shared.h index 40b982d2b06..5578ee301eb 100644 --- a/js/src/jit/shared/AssemblerBuffer-x86-shared.h +++ b/js/src/jit/shared/AssemblerBuffer-x86-shared.h @@ -65,12 +65,6 @@ #define ADDR_o32b(offset, base) ADDR_ob(offset, base) #define ADDR_o32bs(offset, base, index, scale) ADDR_obs(offset, base, index, scale) -#define FIXME_INSN_PRINTING \ - do { \ - spew("FIXME insn printing %s:%d", \ - __FILE__, __LINE__); \ - } while (0) - namespace js { namespace jit { diff --git a/js/src/jit/shared/BaseAssembler-x86-shared.h b/js/src/jit/shared/BaseAssembler-x86-shared.h index 0a596a2266d..03e6e6442cb 100644 --- a/js/src/jit/shared/BaseAssembler-x86-shared.h +++ b/js/src/jit/shared/BaseAssembler-x86-shared.h @@ -297,7 +297,6 @@ private: OP_SUB_EvGv = 0x29, OP_SUB_GvEv = 0x2B, OP_SUB_EAXIv = 0x2D, - PRE_PREDICT_BRANCH_NOT_TAKEN = 0x2E, OP_XOR_EbGb = 0x30, OP_XOR_EvGv = 0x31, OP_XOR_GvEv = 0x33, @@ -701,7 +700,7 @@ public: #ifdef JS_CODEGEN_X86 void adcl_im(int32_t imm, const void* addr) { - FIXME_INSN_PRINTING; + spew("adcl %d, %p", imm, addr); if (CAN_SIGN_EXTEND_8_32(imm)) { m_formatter.oneByteOp(OP_GROUP1_EvIb, addr, GROUP1_OP_ADC); m_formatter.immediate8s(imm); @@ -1135,7 +1134,7 @@ public: void negl_m(int32_t offset, RegisterID base) { - FIXME_INSN_PRINTING; + spew("negl " MEM_ob, ADDR_ob(offset, base)); m_formatter.oneByteOp(OP_GROUP3_Ev, offset, base, GROUP3_OP_NEG); } @@ -1147,7 +1146,7 @@ public: void notl_m(int32_t offset, RegisterID base) { - FIXME_INSN_PRINTING; + spew("notl " MEM_ob, ADDR_ob(offset, base)); m_formatter.oneByteOp(OP_GROUP3_Ev, offset, base, GROUP3_OP_NOT); } @@ -1232,7 +1231,7 @@ public: #else void orl_im(int32_t imm, const void* addr) { - FIXME_INSN_PRINTING; + spew("orl $0x%x, %p", imm, addr); if (CAN_SIGN_EXTEND_8_32(imm)) { m_formatter.oneByteOp(OP_GROUP1_EvIb, addr, GROUP1_OP_OR); m_formatter.immediate8s(imm); @@ -1330,7 +1329,7 @@ public: #else void subl_im(int32_t imm, const void* addr) { - FIXME_INSN_PRINTING; + spew("subl $%d, %p", imm, addr); if (CAN_SIGN_EXTEND_8_32(imm)) { m_formatter.oneByteOp(OP_GROUP1_EvIb, addr, GROUP1_OP_SUB); m_formatter.immediate8s(imm); @@ -1466,7 +1465,7 @@ public: #ifdef JS_CODEGEN_X64 void sarq_CLr(RegisterID dst) { - FIXME_INSN_PRINTING; + spew("sarq %%cl, %s", GPReg64Name(dst)); m_formatter.oneByteOp64(OP_GROUP2_EvCL, dst, GROUP2_OP_SAR); } @@ -1643,7 +1642,7 @@ public: m_formatter.oneByteOp(OP_CMP_GvEv, address, lhs); } - void cmpl_ir(int rhs, RegisterID lhs) + void cmpl_ir(int32_t rhs, RegisterID lhs) { if (rhs == 0) { testl_rr(lhs, lhs); @@ -1663,7 +1662,7 @@ public: } } - void cmpl_i32r(int rhs, RegisterID lhs) + void cmpl_i32r(int32_t rhs, RegisterID lhs) { spew("cmpl $0x%04x, %s", rhs, GPReg32Name(lhs)); if (lhs == X86Registers::eax) @@ -1673,7 +1672,7 @@ public: m_formatter.immediate32(rhs); } - void cmpl_im(int rhs, int32_t offset, RegisterID base) + void cmpl_im(int32_t rhs, int32_t offset, RegisterID base) { spew("cmpl $0x%x, " MEM_ob, rhs, ADDR_ob(offset, base)); if (CAN_SIGN_EXTEND_8_32(rhs)) { @@ -1685,21 +1684,21 @@ public: } } - void cmpb_im(int rhs, int32_t offset, RegisterID base) + void cmpb_im(int32_t rhs, int32_t offset, RegisterID base) { spew("cmpb $0x%x, " MEM_ob, rhs, ADDR_ob(offset, base)); m_formatter.oneByteOp(OP_GROUP1_EbIb, offset, base, GROUP1_OP_CMP); m_formatter.immediate8(rhs); } - void cmpb_im(int rhs, int32_t offset, RegisterID base, RegisterID index, int scale) + void cmpb_im(int32_t rhs, int32_t offset, RegisterID base, RegisterID index, int scale) { spew("cmpb $0x%x, " MEM_obs, rhs, ADDR_obs(offset, base, index, scale)); m_formatter.oneByteOp(OP_GROUP1_EbIb, offset, base, index, scale, GROUP1_OP_CMP); m_formatter.immediate8(rhs); } - void cmpl_im(int rhs, int32_t offset, RegisterID base, RegisterID index, int scale) + void cmpl_im(int32_t rhs, int32_t offset, RegisterID base, RegisterID index, int scale) { spew("cmpl $0x%x, " MEM_o32b, rhs, ADDR_o32b(offset, base)); if (CAN_SIGN_EXTEND_8_32(rhs)) { @@ -1712,7 +1711,7 @@ public: } MOZ_WARN_UNUSED_RESULT JmpSrc - cmpl_im_disp32(int rhs, int32_t offset, RegisterID base) + cmpl_im_disp32(int32_t rhs, int32_t offset, RegisterID base) { spew("cmpl $0x%x, " MEM_o32b, rhs, ADDR_o32b(offset, base)); JmpSrc r; @@ -1729,7 +1728,7 @@ public: } MOZ_WARN_UNUSED_RESULT JmpSrc - cmpl_im_disp32(int rhs, const void *addr) + cmpl_im_disp32(int32_t rhs, const void *addr) { spew("cmpl $0x%x, %p", rhs, addr); JmpSrc r; @@ -1745,14 +1744,14 @@ public: return r; } - void cmpl_i32m(int rhs, int32_t offset, RegisterID base) + void cmpl_i32m(int32_t rhs, int32_t offset, RegisterID base) { spew("cmpl $0x%04x, " MEM_ob, rhs, ADDR_ob(offset, base)); m_formatter.oneByteOp(OP_GROUP1_EvIz, offset, base, GROUP1_OP_CMP); m_formatter.immediate32(rhs); } - void cmpl_i32m(int rhs, const void *addr) + void cmpl_i32m(int32_t rhs, const void *addr) { spew("cmpl $0x%04x, %p", rhs, addr); m_formatter.oneByteOp(OP_GROUP1_EvIz, addr, GROUP1_OP_CMP); @@ -1778,7 +1777,7 @@ public: m_formatter.oneByteOp64(OP_CMP_GvEv, offset, base, lhs); } - void cmpq_ir(int rhs, RegisterID lhs) + void cmpq_ir(int32_t rhs, RegisterID lhs) { if (rhs == 0) { testq_rr(lhs, lhs); @@ -1798,7 +1797,7 @@ public: } } - void cmpq_im(int rhs, int32_t offset, RegisterID base) + void cmpq_im(int32_t rhs, int32_t offset, RegisterID base) { spew("cmpq $0x%" PRIx64 ", " MEM_ob, int64_t(rhs), ADDR_ob(offset, base)); if (CAN_SIGN_EXTEND_8_32(rhs)) { @@ -1810,9 +1809,9 @@ public: } } - void cmpq_im(int rhs, int32_t offset, RegisterID base, RegisterID index, int scale) + void cmpq_im(int32_t rhs, int32_t offset, RegisterID base, RegisterID index, int scale) { - FIXME_INSN_PRINTING; + spew("cmpq $0x%x, " MEM_obs, rhs, ADDR_obs(offset, base, index, scale)); if (CAN_SIGN_EXTEND_8_32(rhs)) { m_formatter.oneByteOp64(OP_GROUP1_EvIb, offset, base, index, scale, GROUP1_OP_CMP); m_formatter.immediate8s(rhs); @@ -1821,7 +1820,7 @@ public: m_formatter.immediate32(rhs); } } - void cmpq_im(int rhs, const void* addr) + void cmpq_im(int32_t rhs, const void* addr) { spew("cmpq $0x%" PRIx64 ", %p", int64_t(rhs), addr); if (CAN_SIGN_EXTEND_8_32(rhs)) { @@ -1850,7 +1849,7 @@ public: m_formatter.oneByteOp_disp32(OP_CMP_EvGv, addr, rhs); } - void cmpl_im(int rhs, const void* addr) + void cmpl_im(int32_t rhs, const void* addr) { spew("cmpl $0x%x, %p", rhs, addr); if (CAN_SIGN_EXTEND_8_32(rhs)) { @@ -1871,14 +1870,14 @@ public: void cmpw_rm(RegisterID rhs, int32_t offset, RegisterID base, RegisterID index, int scale) { - FIXME_INSN_PRINTING; + spew("cmpw %s, " MEM_obs, GPReg16Name(rhs), ADDR_obs(offset, base, index, scale)); m_formatter.prefix(PRE_OPERAND_SIZE); m_formatter.oneByteOp(OP_CMP_EvGv, offset, base, index, scale, rhs); } void cmpw_im(int32_t imm, int32_t offset, RegisterID base, RegisterID index, int scale) { - FIXME_INSN_PRINTING; + spew("cmpw $%d, " MEM_obs, imm, ADDR_obs(offset, base, index, scale)); if (CAN_SIGN_EXTEND_8_32(imm)) { m_formatter.prefix(PRE_OPERAND_SIZE); m_formatter.oneByteOp(OP_GROUP1_EvIb, offset, base, index, scale, GROUP1_OP_CMP); @@ -1902,7 +1901,7 @@ public: m_formatter.oneByteOp(OP_TEST_EbGb, lhs, rhs); } - void testl_ir(int rhs, RegisterID lhs) + void testl_ir(int32_t rhs, RegisterID lhs) { // If the mask fits in an 8-bit immediate, we can use testb with an // 8-bit subreg. @@ -1924,37 +1923,37 @@ public: m_formatter.immediate32(rhs); } - void testl_i32m(int rhs, int32_t offset, RegisterID base) + void testl_i32m(int32_t rhs, int32_t offset, RegisterID base) { spew("testl $0x%x, " MEM_ob, rhs, ADDR_ob(offset, base)); m_formatter.oneByteOp(OP_GROUP3_EvIz, offset, base, GROUP3_OP_TEST); m_formatter.immediate32(rhs); } - void testl_i32m(int rhs, const void *addr) + void testl_i32m(int32_t rhs, const void *addr) { spew("testl $0x%x, %p", rhs, addr); m_formatter.oneByteOp(OP_GROUP3_EvIz, addr, GROUP3_OP_TEST); m_formatter.immediate32(rhs); } - void testb_im(int rhs, int32_t offset, RegisterID base) + void testb_im(int32_t rhs, int32_t offset, RegisterID base) { - FIXME_INSN_PRINTING; + spew("testb $0x%x, " MEM_ob, rhs, ADDR_ob(offset, base)); m_formatter.oneByteOp(OP_GROUP3_EbIb, offset, base, GROUP3_OP_TEST); m_formatter.immediate8(rhs); } - void testb_im(int rhs, int32_t offset, RegisterID base, RegisterID index, int scale) + void testb_im(int32_t rhs, int32_t offset, RegisterID base, RegisterID index, int scale) { - FIXME_INSN_PRINTING; + spew("testb $0x%x, " MEM_obs, rhs, ADDR_obs(offset, base, index, scale)); m_formatter.oneByteOp(OP_GROUP3_EbIb, offset, base, index, scale, GROUP3_OP_TEST); m_formatter.immediate8(rhs); } - void testl_i32m(int rhs, int32_t offset, RegisterID base, RegisterID index, int scale) + void testl_i32m(int32_t rhs, int32_t offset, RegisterID base, RegisterID index, int scale) { - FIXME_INSN_PRINTING; + spew("testl $0x%4x, " MEM_obs, rhs, ADDR_obs(offset, base, index, scale)); m_formatter.oneByteOp(OP_GROUP3_EvIz, offset, base, index, scale, GROUP3_OP_TEST); m_formatter.immediate32(rhs); } @@ -1966,7 +1965,7 @@ public: m_formatter.oneByteOp64(OP_TEST_EvGv, lhs, rhs); } - void testq_ir(int rhs, RegisterID lhs) + void testq_ir(int32_t rhs, RegisterID lhs) { // If the mask fits in a 32-bit immediate, we can use testl with a // 32-bit subreg. @@ -1982,16 +1981,16 @@ public: m_formatter.immediate32(rhs); } - void testq_i32m(int rhs, int32_t offset, RegisterID base) + void testq_i32m(int32_t rhs, int32_t offset, RegisterID base) { spew("testq $0x%" PRIx64 ", " MEM_ob, int64_t(rhs), ADDR_ob(offset, base)); m_formatter.oneByteOp64(OP_GROUP3_EvIz, offset, base, GROUP3_OP_TEST); m_formatter.immediate32(rhs); } - void testq_i32m(int rhs, int32_t offset, RegisterID base, RegisterID index, int scale) + void testq_i32m(int32_t rhs, int32_t offset, RegisterID base, RegisterID index, int scale) { - FIXME_INSN_PRINTING; + spew("testq $0x%4x, " MEM_obs, rhs, ADDR_obs(offset, base, index, scale)); m_formatter.oneByteOp64(OP_GROUP3_EvIz, offset, base, index, scale, GROUP3_OP_TEST); m_formatter.immediate32(rhs); } @@ -1999,12 +1998,12 @@ public: void testw_rr(RegisterID rhs, RegisterID lhs) { - FIXME_INSN_PRINTING; + spew("testw %s, %s", GPReg16Name(rhs), GPReg16Name(lhs)); m_formatter.prefix(PRE_OPERAND_SIZE); m_formatter.oneByteOp(OP_TEST_EvGv, lhs, rhs); } - void testb_ir(int rhs, RegisterID lhs) + void testb_ir(int32_t rhs, RegisterID lhs) { spew("testb $0x%x, %s", rhs, GPReg8Name(lhs)); if (lhs == X86Registers::eax) @@ -2016,7 +2015,7 @@ public: // Like testb_ir, but never emits a REX prefix. This may be used to // reference ah..bh. - void testb_ir_norex(int rhs, RegisterID lhs) + void testb_ir_norex(int32_t rhs, RegisterID lhs) { spew("testb $0x%x, %s", rhs, GPReg8Name_norex(lhs)); m_formatter.oneByteOp8_norex(OP_GROUP3_EbIb, lhs, GROUP3_OP_TEST); @@ -2293,7 +2292,7 @@ public: void movq_rm_disp32(RegisterID src, int32_t offset, RegisterID base) { - FIXME_INSN_PRINTING; + spew("movq %s, " MEM_o32b, GPReg64Name(src), ADDR_o32b(offset, base)); m_formatter.oneByteOp64_disp32(OP_MOV_EvGv, offset, base, src); } @@ -2346,7 +2345,7 @@ public: void movq_mr_disp32(int32_t offset, RegisterID base, RegisterID dst) { - FIXME_INSN_PRINTING; + spew("movq " MEM_o32b ", %s", ADDR_o32b(offset, base), GPReg64Name(dst)); m_formatter.oneByteOp64_disp32(OP_MOV_GvEv, offset, base, dst); } @@ -3785,12 +3784,6 @@ threeByteOpImmSimd("vblendps", VEX_PD, OP3_BLENDPS_VpsWpsIb, ESCAPE_BLENDPS, imm m_formatter.immediate16u(imm); } - void predictNotTaken() - { - FIXME_INSN_PRINTING; - m_formatter.prefix(PRE_PREDICT_BRANCH_NOT_TAKEN); - } - #ifdef JS_CODEGEN_X86 void pusha() { From 16599d84b0fb0b3b0a7ce8eb8e0c1746181f920b Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 30 Jan 2015 16:05:55 -0800 Subject: [PATCH 067/101] Bug 1125202 - SpiderMonkey: Finish the conversion of spew output r=bbouvier --- js/src/jit/shared/AssemblerBuffer-x86-shared.h | 18 ------------------ js/src/jit/shared/BaseAssembler-x86-shared.h | 14 +++++++------- 2 files changed, 7 insertions(+), 25 deletions(-) diff --git a/js/src/jit/shared/AssemblerBuffer-x86-shared.h b/js/src/jit/shared/AssemblerBuffer-x86-shared.h index 5578ee301eb..b583dc90807 100644 --- a/js/src/jit/shared/AssemblerBuffer-x86-shared.h +++ b/js/src/jit/shared/AssemblerBuffer-x86-shared.h @@ -216,24 +216,6 @@ namespace jit { } } } - - static void staticSpew(const char *fmt, ...) -#ifdef __GNUC__ - __attribute__ ((format (printf, 1, 2))) -#endif - { - if (js::jit::JitSpewEnabled(js::jit::JitSpew_Codegen)) { - char buf[200]; - - va_list va; - va_start(va, fmt); - int i = vsnprintf(buf, sizeof(buf), fmt, va); - va_end(va); - - if (i > -1) - js::jit::JitSpew(js::jit::JitSpew_Codegen, "%s", buf); - } - } }; } // namespace jit diff --git a/js/src/jit/shared/BaseAssembler-x86-shared.h b/js/src/jit/shared/BaseAssembler-x86-shared.h index 03e6e6442cb..bac882c10fc 100644 --- a/js/src/jit/shared/BaseAssembler-x86-shared.h +++ b/js/src/jit/shared/BaseAssembler-x86-shared.h @@ -3821,18 +3821,20 @@ threeByteOpImmSimd("vblendps", VEX_PD, OP3_BLENDPS_VpsWpsIb, ESCAPE_BLENDPS, imm return JmpDst(jump.offset() + offset); } - JmpDst align(int alignment) + void align(int alignment) { spew(".balign %d, 0x%x # hlt", alignment, OP_HLT); while (!m_formatter.isAligned(alignment)) m_formatter.oneByteOp(OP_HLT); - - return label(); } void jumpTablePointer(uintptr_t ptr) { - spew("#jumpTablePointer %llu", (unsigned long long)ptr); +#ifdef JS_CODEGEN_X64 + spew(".quad 0x%" PRIxPTR, ptr); +#else + spew(".int 0x%" PRIxPTR, ptr); +#endif m_formatter.jumpTablePointer(ptr); } @@ -3913,7 +3915,7 @@ threeByteOpImmSimd("vblendps", VEX_PD, OP3_BLENDPS_VpsWpsIb, ESCAPE_BLENDPS, imm if (oom()) return; - spew("##link ((%d)) jumps to ((%d))", from.offset(), to.offset()); + spew(".set .Lfrom%d, .Llabel%d", from.offset(), to.offset()); unsigned char* code = m_formatter.data(); setRel32(code + from.offset(), code + to.offset()); } @@ -3937,7 +3939,6 @@ threeByteOpImmSimd("vblendps", VEX_PD, OP3_BLENDPS_VpsWpsIb, ESCAPE_BLENDPS, imm if (offset != static_cast(offset)) MOZ_CRASH("offset is too great for a 32-bit relocation"); - staticSpew("##setRel32 ((from=%p)) ((to=%p))", from, to); setInt32(from, offset); } @@ -3959,7 +3960,6 @@ threeByteOpImmSimd("vblendps", VEX_PD, OP3_BLENDPS_VpsWpsIb, ESCAPE_BLENDPS, imm static void setPointer(void* where, const void* value) { - staticSpew("##setPtr ((where=%p)) ((value=%p))", where, value); static_cast(where)[-1] = value; } From 3686986dac9e94dd2faa54f8b81ccbf3a946ac95 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 30 Jan 2015 16:05:55 -0800 Subject: [PATCH 068/101] Bug 1125202 - SpiderMonkey: Move AssemberBuffer spew code out of line r=h4writer --- .../jit/shared/AssemblerBuffer-x86-shared.cpp | 24 ++++++++++++++ .../jit/shared/AssemblerBuffer-x86-shared.h | 32 ++++++------------- js/src/moz.build | 1 + 3 files changed, 34 insertions(+), 23 deletions(-) create mode 100644 js/src/jit/shared/AssemblerBuffer-x86-shared.cpp diff --git a/js/src/jit/shared/AssemblerBuffer-x86-shared.cpp b/js/src/jit/shared/AssemblerBuffer-x86-shared.cpp new file mode 100644 index 00000000000..3ef9d3ed616 --- /dev/null +++ b/js/src/jit/shared/AssemblerBuffer-x86-shared.cpp @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "jit/shared/AssemblerBuffer-x86-shared.h" + +#include "jsopcode.h" + +void js::jit::GenericAssembler::spew(const char *fmt, va_list va) +{ + // Buffer to hold the formatted string. Note that this may contain + // '%' characters, so do not pass it directly to printf functions. + char buf[200]; + + int i = vsnprintf(buf, sizeof(buf), fmt, va); + + if (i > -1) { + if (printer) + printer->printf("%s\n", buf); + js::jit::JitSpew(js::jit::JitSpew_Codegen, "%s", buf); + } +} diff --git a/js/src/jit/shared/AssemblerBuffer-x86-shared.h b/js/src/jit/shared/AssemblerBuffer-x86-shared.h index b583dc90807..c9128c6efb4 100644 --- a/js/src/jit/shared/AssemblerBuffer-x86-shared.h +++ b/js/src/jit/shared/AssemblerBuffer-x86-shared.h @@ -30,17 +30,11 @@ #ifndef jit_shared_AssemblerBuffer_x86_shared_h #define jit_shared_AssemblerBuffer_x86_shared_h -#include #include #include -#include "jsfriendapi.h" -#include "jsopcode.h" -#include "jsutil.h" - #include "jit/ExecutableAllocator.h" #include "jit/JitSpewer.h" -#include "js/RootingAPI.h" // Spew formatting helpers. #define PRETTYHEX(x) (((x)<0)?"-":""),(((x)<0)?-(x):(x)) @@ -66,6 +60,9 @@ #define ADDR_o32bs(offset, base, index, scale) ADDR_obs(offset, base, index, scale) namespace js { + + class Sprinter; + namespace jit { class AssemblerBuffer { @@ -179,18 +176,15 @@ namespace jit { class GenericAssembler { - js::Sprinter *printer; + Sprinter *printer; public: - bool isOOLPath; - GenericAssembler() : printer(NULL) - , isOOLPath(false) {} - void setPrinter(js::Sprinter *sp) { + void setPrinter(Sprinter *sp) { printer = sp; } @@ -199,23 +193,15 @@ namespace jit { __attribute__ ((format (printf, 2, 3))) #endif { - if (printer || js::jit::JitSpewEnabled(js::jit::JitSpew_Codegen)) { - // Buffer to hold the formatted string. Note that this may contain - // '%' characters, so do not pass it directly to printf functions. - char buf[200]; - + if (MOZ_UNLIKELY(printer || JitSpewEnabled(JitSpew_Codegen))) { va_list va; va_start(va, fmt); - int i = vsnprintf(buf, sizeof(buf), fmt, va); + spew(fmt, va); va_end(va); - - if (i > -1) { - if (printer) - printer->printf("%s\n", buf); - js::jit::JitSpew(js::jit::JitSpew_Codegen, "%s", buf); - } } } + + MOZ_COLD void spew(const char *fmt, va_list va); }; } // namespace jit diff --git a/js/src/moz.build b/js/src/moz.build index f46ee9bc725..e93d2eff71a 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -322,6 +322,7 @@ if not CONFIG['ENABLE_ION']: elif CONFIG['JS_CODEGEN_X86'] or CONFIG['JS_CODEGEN_X64']: UNIFIED_SOURCES += [ 'jit/shared/Assembler-x86-shared.cpp', + 'jit/shared/AssemblerBuffer-x86-shared.cpp', 'jit/shared/BaselineCompiler-x86-shared.cpp', 'jit/shared/BaselineIC-x86-shared.cpp', 'jit/shared/CodeGenerator-x86-shared.cpp', From 818a066bd814a7333aa9943dd0ffbb0c83501231 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 30 Jan 2015 16:05:55 -0800 Subject: [PATCH 069/101] Bug 1125202 - SpiderMonkey: Minimize diffs between Architecture-x64.h and Architecture-x86.h r=mjrosenb --- js/src/jit/x64/Architecture-x64.h | 11 ++++------- js/src/jit/x86/Architecture-x86.h | 8 +++----- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/js/src/jit/x64/Architecture-x64.h b/js/src/jit/x64/Architecture-x64.h index df32cae23d4..8087501b3f8 100644 --- a/js/src/jit/x64/Architecture-x64.h +++ b/js/src/jit/x64/Architecture-x64.h @@ -106,11 +106,11 @@ class Registers { (1 << X86Registers::esp) | (1 << X86Registers::r11); // This is ScratchReg. + static const uint32_t AllocatableMask = AllMask & ~NonAllocatableMask; + // Registers that can be allocated without being saved, generally. static const uint32_t TempMask = VolatileMask & ~NonAllocatableMask; - static const uint32_t AllocatableMask = AllMask & ~NonAllocatableMask; - // Registers returned from a JS -> JS call. static const uint32_t JSCallMask = (1 << X86Registers::ecx); @@ -169,10 +169,9 @@ class FloatRegisters { static const uint32_t WrapperMask = VolatileMask; static const uint32_t NonAllocatableMask = - (1 << X86Registers::xmm15); // This is ScratchFloatReg. + (1 << X86Registers::xmm15); // This is ScratchDoubleReg. static const uint32_t AllocatableMask = AllMask & ~NonAllocatableMask; - }; template @@ -221,7 +220,6 @@ struct FloatRegister { uint32_t numAliased() const { return 1; } - // N.B. FloatRegister is an explicit outparam here because msvc-2010 // miscompiled it on win64 when the value was simply returned void aliased(uint32_t aliasIdx, FloatRegister *ret) { @@ -239,7 +237,7 @@ struct FloatRegister { uint32_t size() const { return sizeof(double); } - uint32_t numAlignedAliased() { + uint32_t numAlignedAliased() const { return 1; } void alignedAliased(uint32_t aliasIdx, FloatRegister *ret) { @@ -250,7 +248,6 @@ struct FloatRegister { static uint32_t GetSizeInBytes(const TypedRegisterSet &s); static uint32_t GetPushSizeInBytes(const TypedRegisterSet &s); uint32_t getRegisterDumpOffsetInBytes(); - }; // Arm/D32 has double registers that can NOT be treated as float32 diff --git a/js/src/jit/x86/Architecture-x86.h b/js/src/jit/x86/Architecture-x86.h index 38b749e5db8..05e3a59fb4d 100644 --- a/js/src/jit/x86/Architecture-x86.h +++ b/js/src/jit/x86/Architecture-x86.h @@ -146,7 +146,7 @@ class FloatRegisters { static const uint32_t WrapperMask = VolatileMask; static const uint32_t NonAllocatableMask = - (1 << X86Registers::xmm7); + (1 << X86Registers::xmm7); // This is ScratchDoubleReg. static const uint32_t AllocatableMask = AllMask & ~NonAllocatableMask; }; @@ -185,10 +185,10 @@ struct FloatRegister { bool volatile_() const { return !!((1 << code()) & FloatRegisters::VolatileMask); } - bool operator != (FloatRegister other) const { + bool operator !=(FloatRegister other) const { return other.code_ != code_; } - bool operator == (FloatRegister other) const { + bool operator ==(FloatRegister other) const { return other.code_ == code_; } bool aliases(FloatRegister other) const { @@ -225,8 +225,6 @@ struct FloatRegister { static uint32_t GetSizeInBytes(const TypedRegisterSet &s); static uint32_t GetPushSizeInBytes(const TypedRegisterSet &s); uint32_t getRegisterDumpOffsetInBytes(); - - }; // Arm/D32 has double registers that can NOT be treated as float32 From bf333f119918204ee866fae3634e3994406125c6 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 30 Jan 2015 16:05:55 -0800 Subject: [PATCH 070/101] Bug 1125202 - SpiderMonkey: Eliminate twoByteRipOpSimd's ripOffset argument r=bbouvier --- js/src/jit/shared/BaseAssembler-x86-shared.h | 32 ++++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/js/src/jit/shared/BaseAssembler-x86-shared.h b/js/src/jit/shared/BaseAssembler-x86-shared.h index bac882c10fc..2993a77497b 100644 --- a/js/src/jit/shared/BaseAssembler-x86-shared.h +++ b/js/src/jit/shared/BaseAssembler-x86-shared.h @@ -3288,32 +3288,32 @@ public: MOZ_WARN_UNUSED_RESULT JmpSrc vmovsd_ripr(XMMRegisterID dst) { - return twoByteRipOpSimd("vmovsd", VEX_SD, OP2_MOVSD_VsdWsd, 0, X86Registers::invalid_xmm, dst); + return twoByteRipOpSimd("vmovsd", VEX_SD, OP2_MOVSD_VsdWsd, X86Registers::invalid_xmm, dst); } MOZ_WARN_UNUSED_RESULT JmpSrc vmovss_ripr(XMMRegisterID dst) { - return twoByteRipOpSimd("vmovss", VEX_SS, OP2_MOVSD_VsdWsd, 0, X86Registers::invalid_xmm, dst); + return twoByteRipOpSimd("vmovss", VEX_SS, OP2_MOVSD_VsdWsd, X86Registers::invalid_xmm, dst); } MOZ_WARN_UNUSED_RESULT JmpSrc vmovsd_rrip(XMMRegisterID src) { - return twoByteRipOpSimd("vmovsd", VEX_SD, OP2_MOVSD_WsdVsd, 0, X86Registers::invalid_xmm, src); + return twoByteRipOpSimd("vmovsd", VEX_SD, OP2_MOVSD_WsdVsd, X86Registers::invalid_xmm, src); } MOZ_WARN_UNUSED_RESULT JmpSrc vmovss_rrip(XMMRegisterID src) { - return twoByteRipOpSimd("vmovss", VEX_SS, OP2_MOVSD_WsdVsd, 0, X86Registers::invalid_xmm, src); + return twoByteRipOpSimd("vmovss", VEX_SS, OP2_MOVSD_WsdVsd, X86Registers::invalid_xmm, src); } MOZ_WARN_UNUSED_RESULT JmpSrc vmovdqa_rrip(XMMRegisterID src) { - return twoByteRipOpSimd("vmovdqa", VEX_PD, OP2_MOVDQ_WdqVdq, 0, X86Registers::invalid_xmm, src); + return twoByteRipOpSimd("vmovdqa", VEX_PD, OP2_MOVDQ_WdqVdq, X86Registers::invalid_xmm, src); } MOZ_WARN_UNUSED_RESULT JmpSrc vmovaps_rrip(XMMRegisterID src) { - return twoByteRipOpSimd("vmovdqa", VEX_PS, OP2_MOVAPS_WsdVsd, 0, X86Registers::invalid_xmm, src); + return twoByteRipOpSimd("vmovdqa", VEX_PS, OP2_MOVAPS_WsdVsd, X86Registers::invalid_xmm, src); } #endif @@ -3392,13 +3392,13 @@ public: MOZ_WARN_UNUSED_RESULT JmpSrc vmovaps_ripr(XMMRegisterID dst) { - return twoByteRipOpSimd("vmovaps", VEX_PS, OP2_MOVAPS_VsdWsd, 0, X86Registers::invalid_xmm, dst); + return twoByteRipOpSimd("vmovaps", VEX_PS, OP2_MOVAPS_VsdWsd, X86Registers::invalid_xmm, dst); } MOZ_WARN_UNUSED_RESULT JmpSrc vmovdqa_ripr(XMMRegisterID dst) { - return twoByteRipOpSimd("vmovdqa", VEX_PD, OP2_MOVDQ_VdqWdq, 0, X86Registers::invalid_xmm, dst); + return twoByteRipOpSimd("vmovdqa", VEX_PD, OP2_MOVDQ_VdqWdq, X86Registers::invalid_xmm, dst); } #else void vmovaps_mr(const void* address, XMMRegisterID dst) @@ -4040,28 +4040,28 @@ private: #ifdef JS_CODEGEN_X64 MOZ_WARN_UNUSED_RESULT JmpSrc twoByteRipOpSimd(const char *name, VexOperandType ty, TwoByteOpcodeID opcode, - int ripOffset, XMMRegisterID src0, XMMRegisterID dst) + XMMRegisterID src0, XMMRegisterID dst) { if (useLegacySSEEncoding(src0, dst)) { m_formatter.legacySSEPrefix(ty); - m_formatter.twoByteRipOp(opcode, ripOffset, dst); + m_formatter.twoByteRipOp(opcode, 0, dst); JmpSrc label(m_formatter.size()); if (IsXMMReversedOperands(opcode)) - spew("%-11s%s, .Lfrom%d%+d(%%rip)", legacySSEOpName(name), XMMRegName(dst), label.offset(), ripOffset); + spew("%-11s%s, .Lfrom%d(%%rip)", legacySSEOpName(name), XMMRegName(dst), label.offset()); else - spew("%-11s.Lfrom%d%+d(%%rip), %s", legacySSEOpName(name), label.offset(), ripOffset, XMMRegName(dst)); + spew("%-11s.Lfrom%d(%%rip), %s", legacySSEOpName(name), label.offset(), XMMRegName(dst)); return label; } - m_formatter.twoByteRipOpVex(ty, opcode, ripOffset, src0, dst); + m_formatter.twoByteRipOpVex(ty, opcode, 0, src0, dst); JmpSrc label(m_formatter.size()); if (src0 == X86Registers::invalid_xmm) { if (IsXMMReversedOperands(opcode)) - spew("%-11s%s, .Lfrom%d%+d(%%rip)", name, XMMRegName(dst), label.offset(), ripOffset); + spew("%-11s%s, .Lfrom%d(%%rip)", name, XMMRegName(dst), label.offset()); else - spew("%-11s.Lfrom%d%+d(%%rip), %s", name, label.offset(), ripOffset, XMMRegName(dst)); + spew("%-11s.Lfrom%d(%%rip), %s", name, label.offset(), XMMRegName(dst)); } else { - spew("%-11s.Lfrom%d%+d(%%rip), %s, %s", name, label.offset(), ripOffset, XMMRegName(src0), XMMRegName(dst)); + spew("%-11s.Lfrom%d(%%rip), %s, %s", name, label.offset(), XMMRegName(src0), XMMRegName(dst)); } return label; } From be0b62ead8837a31ad866df8191fb1b1636118c0 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 30 Jan 2015 16:05:56 -0800 Subject: [PATCH 071/101] Bug 1125202 - SpiderMonkey: Add formatting macros for rip-relative memory operands r=bbouvier --- js/src/jit/shared/AssemblerBuffer-x86-shared.h | 2 ++ js/src/jit/shared/BaseAssembler-x86-shared.h | 18 +++++++++--------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/js/src/jit/shared/AssemblerBuffer-x86-shared.h b/js/src/jit/shared/AssemblerBuffer-x86-shared.h index c9128c6efb4..a3398a93e99 100644 --- a/js/src/jit/shared/AssemblerBuffer-x86-shared.h +++ b/js/src/jit/shared/AssemblerBuffer-x86-shared.h @@ -48,6 +48,7 @@ #define MEM_o32s MEM_o32 "(,%s,%d)" #define MEM_o32b MEM_o32 "(%s)" #define MEM_o32bs MEM_o32 "(%s,%s,%d)" +#define MEM_o32r ".Lfrom%d(%%rip)" #define ADDR_o(offset) PRETTYHEX(offset) #define ADDR_os(offset, index, scale) ADDR_o(offset), GPRegName((index)), (1<<(scale)) @@ -58,6 +59,7 @@ #define ADDR_o32s(offset, index, scale) ADDR_os(offset, index, scale) #define ADDR_o32b(offset, base) ADDR_ob(offset, base) #define ADDR_o32bs(offset, base, index, scale) ADDR_obs(offset, base, index, scale) +#define ADDR_o32r(offset) (offset) namespace js { diff --git a/js/src/jit/shared/BaseAssembler-x86-shared.h b/js/src/jit/shared/BaseAssembler-x86-shared.h index 2993a77497b..4be3804e450 100644 --- a/js/src/jit/shared/BaseAssembler-x86-shared.h +++ b/js/src/jit/shared/BaseAssembler-x86-shared.h @@ -2422,7 +2422,7 @@ public: { m_formatter.oneByteRipOp(OP_MOV_GvEv, 0, (RegisterID)dst); JmpSrc label(m_formatter.size()); - spew("movl .Lfrom%d(%%rip), %s", label.offset(), GPReg32Name(dst)); + spew("movl " MEM_o32r ", %s", ADDR_o32r(label.offset()), GPReg32Name(dst)); return label; } @@ -2431,7 +2431,7 @@ public: { m_formatter.oneByteRipOp(OP_MOV_EvGv, 0, (RegisterID)src); JmpSrc label(m_formatter.size()); - spew("movl %s, .Lfrom%d(%%rip)", GPReg32Name(src), label.offset()); + spew("movl %s, " MEM_o32r "", GPReg32Name(src), ADDR_o32r(label.offset())); return label; } @@ -2440,7 +2440,7 @@ public: { m_formatter.oneByteRipOp64(OP_MOV_GvEv, 0, dst); JmpSrc label(m_formatter.size()); - spew("movq .Lfrom%d(%%rip), %s", label.offset(), GPRegName(dst)); + spew("movq " MEM_o32r ", %s", ADDR_o32r(label.offset()), GPRegName(dst)); return label; } #endif @@ -2645,7 +2645,7 @@ public: { m_formatter.oneByteRipOp64(OP_LEA, 0, dst); JmpSrc label(m_formatter.size()); - spew("leaq .Lfrom%d(%%rip), %s", label.offset(), GPRegName(dst)); + spew("leaq " MEM_o32r ", %s", ADDR_o32r(label.offset()), GPRegName(dst)); return label; } #endif @@ -4047,9 +4047,9 @@ private: m_formatter.twoByteRipOp(opcode, 0, dst); JmpSrc label(m_formatter.size()); if (IsXMMReversedOperands(opcode)) - spew("%-11s%s, .Lfrom%d(%%rip)", legacySSEOpName(name), XMMRegName(dst), label.offset()); + spew("%-11s%s, " MEM_o32r "", legacySSEOpName(name), XMMRegName(dst), ADDR_o32r(label.offset())); else - spew("%-11s.Lfrom%d(%%rip), %s", legacySSEOpName(name), label.offset(), XMMRegName(dst)); + spew("%-11s" MEM_o32r ", %s", legacySSEOpName(name), ADDR_o32r(label.offset()), XMMRegName(dst)); return label; } @@ -4057,11 +4057,11 @@ private: JmpSrc label(m_formatter.size()); if (src0 == X86Registers::invalid_xmm) { if (IsXMMReversedOperands(opcode)) - spew("%-11s%s, .Lfrom%d(%%rip)", name, XMMRegName(dst), label.offset()); + spew("%-11s%s, " MEM_o32r "", name, XMMRegName(dst), ADDR_o32r(label.offset())); else - spew("%-11s.Lfrom%d(%%rip), %s", name, label.offset(), XMMRegName(dst)); + spew("%-11s" MEM_o32r ", %s", name, ADDR_o32r(label.offset()), XMMRegName(dst)); } else { - spew("%-11s.Lfrom%d(%%rip), %s, %s", name, label.offset(), XMMRegName(src0), XMMRegName(dst)); + spew("%-11s" MEM_o32r ", %s, %s", name, ADDR_o32r(label.offset()), XMMRegName(src0), XMMRegName(dst)); } return label; } From d220a99627bfcc06918f3261fe9d6bf830fb3175 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 30 Jan 2015 16:05:56 -0800 Subject: [PATCH 072/101] Bug 1125202 - SpiderMonkey: Reorganize the x86 BaseAssembler codebase r=jandem --- js/src/asmjs/AsmJSModule.cpp | 18 +- js/src/asmjs/AsmJSSignalHandlers.cpp | 128 +- js/src/jit/shared/Assembler-x86-shared.cpp | 2 +- js/src/jit/shared/Assembler-x86-shared.h | 84 +- js/src/jit/shared/BaseAssembler-x86-shared.h | 1180 ++++------------- .../jit/shared/CodeGenerator-x86-shared.cpp | 12 +- js/src/jit/shared/Constants-x86-shared.h | 222 ++++ js/src/jit/shared/Encoding-x86-shared.h | 317 +++++ js/src/jit/shared/MacroAssembler-x86-shared.h | 6 +- js/src/jit/shared/Patching-x86-shared.h | 137 ++ js/src/jit/x64/Architecture-x64.h | 86 +- js/src/jit/x64/Assembler-x64.cpp | 22 +- js/src/jit/x64/Assembler-x64.h | 76 +- js/src/jit/x64/MacroAssembler-x64.h | 20 +- js/src/jit/x86/Architecture-x86.h | 54 +- js/src/jit/x86/Assembler-x86.cpp | 4 +- js/src/jit/x86/Assembler-x86.h | 48 +- 17 files changed, 1224 insertions(+), 1192 deletions(-) create mode 100644 js/src/jit/shared/Constants-x86-shared.h create mode 100644 js/src/jit/shared/Encoding-x86-shared.h create mode 100644 js/src/jit/shared/Patching-x86-shared.h diff --git a/js/src/asmjs/AsmJSModule.cpp b/js/src/asmjs/AsmJSModule.cpp index b065064a4c6..e4d4f5951bb 100644 --- a/js/src/asmjs/AsmJSModule.cpp +++ b/js/src/asmjs/AsmJSModule.cpp @@ -784,13 +784,13 @@ AsmJSModule::initHeap(Handle heap, JSContext *cx // i.e. ptr >= heapLength + 1 - data-type-byte-size // (Note that we need >= as this is what codegen uses.) size_t scalarByteSize = TypedArrayElemSize(access.type()); - X86Assembler::setPointer(access.patchLengthAt(code_), - (void*)(heap->byteLength() + 1 - scalarByteSize)); + X86Encoding::SetPointer(access.patchLengthAt(code_), + (void*)(heap->byteLength() + 1 - scalarByteSize)); } void *addr = access.patchOffsetAt(code_); - uint32_t disp = reinterpret_cast(X86Assembler::getPointer(addr)); + uint32_t disp = reinterpret_cast(X86Encoding::GetPointer(addr)); MOZ_ASSERT(disp <= INT32_MAX); - X86Assembler::setPointer(addr, (void *)(heapOffset + disp)); + X86Encoding::SetPointer(addr, (void *)(heapOffset + disp)); } #elif defined(JS_CODEGEN_X64) // Even with signal handling being used for most bounds checks, there may be @@ -806,7 +806,7 @@ AsmJSModule::initHeap(Handle heap, JSContext *cx if (access.hasLengthCheck()) { // See comment above for x86 codegen. size_t scalarByteSize = TypedArrayElemSize(access.type()); - X86Assembler::setInt32(access.patchLengthAt(code_), heapLength + 1 - scalarByteSize); + X86Encoding::SetInt32(access.patchLengthAt(code_), heapLength + 1 - scalarByteSize); } } #elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS) @@ -828,9 +828,9 @@ AsmJSModule::restoreHeapToInitialState(ArrayBufferObjectMaybeShared *maybePrevBu for (unsigned i = 0; i < heapAccesses_.length(); i++) { const jit::AsmJSHeapAccess &access = heapAccesses_[i]; void *addr = access.patchOffsetAt(code_); - uint8_t *ptr = reinterpret_cast(X86Assembler::getPointer(addr)); + uint8_t *ptr = reinterpret_cast(X86Encoding::GetPointer(addr)); MOZ_ASSERT(ptr >= ptrBase); - X86Assembler::setPointer(addr, (void *)(ptr - ptrBase)); + X86Encoding::SetPointer(addr, (void *)(ptr - ptrBase)); } } #endif @@ -1678,7 +1678,7 @@ AsmJSModule::setProfilingEnabled(bool enabled, JSContext *cx) uint8_t *callerRetAddr = code_ + cs.returnAddressOffset(); #if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64) - void *callee = X86Assembler::getRel32Target(callerRetAddr); + void *callee = X86Encoding::GetRel32Target(callerRetAddr); #elif defined(JS_CODEGEN_ARM) uint8_t *caller = callerRetAddr - 4; Instruction *callerInsn = reinterpret_cast(caller); @@ -1706,7 +1706,7 @@ AsmJSModule::setProfilingEnabled(bool enabled, JSContext *cx) uint8_t *newCallee = enabled ? profilingEntry : entry; #if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64) - X86Assembler::setRel32(callerRetAddr, newCallee); + X86Encoding::SetRel32(callerRetAddr, newCallee); #elif defined(JS_CODEGEN_ARM) new (caller) InstBLImm(BOffImm(newCallee - caller), Assembler::Always); #elif defined(JS_CODEGEN_MIPS) diff --git a/js/src/asmjs/AsmJSSignalHandlers.cpp b/js/src/asmjs/AsmJSSignalHandlers.cpp index 68ac55c66a5..35400435007 100644 --- a/js/src/asmjs/AsmJSSignalHandlers.cpp +++ b/js/src/asmjs/AsmJSSignalHandlers.cpp @@ -382,42 +382,42 @@ SetRegisterToCoercedUndefined(CONTEXT *context, Scalar::Type viewType, AnyRegist { if (reg.isFloat()) { switch (reg.fpu().code()) { - case X86Registers::xmm0: SetXMMRegToNaN(viewType, &XMM_sig(context, 0)); break; - case X86Registers::xmm1: SetXMMRegToNaN(viewType, &XMM_sig(context, 1)); break; - case X86Registers::xmm2: SetXMMRegToNaN(viewType, &XMM_sig(context, 2)); break; - case X86Registers::xmm3: SetXMMRegToNaN(viewType, &XMM_sig(context, 3)); break; - case X86Registers::xmm4: SetXMMRegToNaN(viewType, &XMM_sig(context, 4)); break; - case X86Registers::xmm5: SetXMMRegToNaN(viewType, &XMM_sig(context, 5)); break; - case X86Registers::xmm6: SetXMMRegToNaN(viewType, &XMM_sig(context, 6)); break; - case X86Registers::xmm7: SetXMMRegToNaN(viewType, &XMM_sig(context, 7)); break; - case X86Registers::xmm8: SetXMMRegToNaN(viewType, &XMM_sig(context, 8)); break; - case X86Registers::xmm9: SetXMMRegToNaN(viewType, &XMM_sig(context, 9)); break; - case X86Registers::xmm10: SetXMMRegToNaN(viewType, &XMM_sig(context, 10)); break; - case X86Registers::xmm11: SetXMMRegToNaN(viewType, &XMM_sig(context, 11)); break; - case X86Registers::xmm12: SetXMMRegToNaN(viewType, &XMM_sig(context, 12)); break; - case X86Registers::xmm13: SetXMMRegToNaN(viewType, &XMM_sig(context, 13)); break; - case X86Registers::xmm14: SetXMMRegToNaN(viewType, &XMM_sig(context, 14)); break; - case X86Registers::xmm15: SetXMMRegToNaN(viewType, &XMM_sig(context, 15)); break; + case X86Encoding::xmm0: SetXMMRegToNaN(viewType, &XMM_sig(context, 0)); break; + case X86Encoding::xmm1: SetXMMRegToNaN(viewType, &XMM_sig(context, 1)); break; + case X86Encoding::xmm2: SetXMMRegToNaN(viewType, &XMM_sig(context, 2)); break; + case X86Encoding::xmm3: SetXMMRegToNaN(viewType, &XMM_sig(context, 3)); break; + case X86Encoding::xmm4: SetXMMRegToNaN(viewType, &XMM_sig(context, 4)); break; + case X86Encoding::xmm5: SetXMMRegToNaN(viewType, &XMM_sig(context, 5)); break; + case X86Encoding::xmm6: SetXMMRegToNaN(viewType, &XMM_sig(context, 6)); break; + case X86Encoding::xmm7: SetXMMRegToNaN(viewType, &XMM_sig(context, 7)); break; + case X86Encoding::xmm8: SetXMMRegToNaN(viewType, &XMM_sig(context, 8)); break; + case X86Encoding::xmm9: SetXMMRegToNaN(viewType, &XMM_sig(context, 9)); break; + case X86Encoding::xmm10: SetXMMRegToNaN(viewType, &XMM_sig(context, 10)); break; + case X86Encoding::xmm11: SetXMMRegToNaN(viewType, &XMM_sig(context, 11)); break; + case X86Encoding::xmm12: SetXMMRegToNaN(viewType, &XMM_sig(context, 12)); break; + case X86Encoding::xmm13: SetXMMRegToNaN(viewType, &XMM_sig(context, 13)); break; + case X86Encoding::xmm14: SetXMMRegToNaN(viewType, &XMM_sig(context, 14)); break; + case X86Encoding::xmm15: SetXMMRegToNaN(viewType, &XMM_sig(context, 15)); break; default: MOZ_CRASH(); } } else { switch (reg.gpr().code()) { - case X86Registers::eax: RAX_sig(context) = 0; break; - case X86Registers::ecx: RCX_sig(context) = 0; break; - case X86Registers::edx: RDX_sig(context) = 0; break; - case X86Registers::ebx: RBX_sig(context) = 0; break; - case X86Registers::esp: RSP_sig(context) = 0; break; - case X86Registers::ebp: RBP_sig(context) = 0; break; - case X86Registers::esi: RSI_sig(context) = 0; break; - case X86Registers::edi: RDI_sig(context) = 0; break; - case X86Registers::r8: R8_sig(context) = 0; break; - case X86Registers::r9: R9_sig(context) = 0; break; - case X86Registers::r10: R10_sig(context) = 0; break; - case X86Registers::r11: R11_sig(context) = 0; break; - case X86Registers::r12: R12_sig(context) = 0; break; - case X86Registers::r13: R13_sig(context) = 0; break; - case X86Registers::r14: R14_sig(context) = 0; break; - case X86Registers::r15: R15_sig(context) = 0; break; + case X86Encoding::rax: RAX_sig(context) = 0; break; + case X86Encoding::rcx: RCX_sig(context) = 0; break; + case X86Encoding::rdx: RDX_sig(context) = 0; break; + case X86Encoding::rbx: RBX_sig(context) = 0; break; + case X86Encoding::rsp: RSP_sig(context) = 0; break; + case X86Encoding::rbp: RBP_sig(context) = 0; break; + case X86Encoding::rsi: RSI_sig(context) = 0; break; + case X86Encoding::rdi: RDI_sig(context) = 0; break; + case X86Encoding::r8: R8_sig(context) = 0; break; + case X86Encoding::r9: R9_sig(context) = 0; break; + case X86Encoding::r10: R10_sig(context) = 0; break; + case X86Encoding::r11: R11_sig(context) = 0; break; + case X86Encoding::r12: R12_sig(context) = 0; break; + case X86Encoding::r13: R13_sig(context) = 0; break; + case X86Encoding::r14: R14_sig(context) = 0; break; + case X86Encoding::r15: R15_sig(context) = 0; break; default: MOZ_CRASH(); } } @@ -551,22 +551,22 @@ SetRegisterToCoercedUndefined(mach_port_t rtThread, x86_thread_state64_t &state, Scalar::Type viewType = heapAccess.type(); switch (heapAccess.loadedReg().fpu().code()) { - case X86Registers::xmm0: SetXMMRegToNaN(viewType, &fstate.__fpu_xmm0); break; - case X86Registers::xmm1: SetXMMRegToNaN(viewType, &fstate.__fpu_xmm1); break; - case X86Registers::xmm2: SetXMMRegToNaN(viewType, &fstate.__fpu_xmm2); break; - case X86Registers::xmm3: SetXMMRegToNaN(viewType, &fstate.__fpu_xmm3); break; - case X86Registers::xmm4: SetXMMRegToNaN(viewType, &fstate.__fpu_xmm4); break; - case X86Registers::xmm5: SetXMMRegToNaN(viewType, &fstate.__fpu_xmm5); break; - case X86Registers::xmm6: SetXMMRegToNaN(viewType, &fstate.__fpu_xmm6); break; - case X86Registers::xmm7: SetXMMRegToNaN(viewType, &fstate.__fpu_xmm7); break; - case X86Registers::xmm8: SetXMMRegToNaN(viewType, &fstate.__fpu_xmm8); break; - case X86Registers::xmm9: SetXMMRegToNaN(viewType, &fstate.__fpu_xmm9); break; - case X86Registers::xmm10: SetXMMRegToNaN(viewType, &fstate.__fpu_xmm10); break; - case X86Registers::xmm11: SetXMMRegToNaN(viewType, &fstate.__fpu_xmm11); break; - case X86Registers::xmm12: SetXMMRegToNaN(viewType, &fstate.__fpu_xmm12); break; - case X86Registers::xmm13: SetXMMRegToNaN(viewType, &fstate.__fpu_xmm13); break; - case X86Registers::xmm14: SetXMMRegToNaN(viewType, &fstate.__fpu_xmm14); break; - case X86Registers::xmm15: SetXMMRegToNaN(viewType, &fstate.__fpu_xmm15); break; + case X86Encoding::xmm0: SetXMMRegToNaN(viewType, &fstate.__fpu_xmm0); break; + case X86Encoding::xmm1: SetXMMRegToNaN(viewType, &fstate.__fpu_xmm1); break; + case X86Encoding::xmm2: SetXMMRegToNaN(viewType, &fstate.__fpu_xmm2); break; + case X86Encoding::xmm3: SetXMMRegToNaN(viewType, &fstate.__fpu_xmm3); break; + case X86Encoding::xmm4: SetXMMRegToNaN(viewType, &fstate.__fpu_xmm4); break; + case X86Encoding::xmm5: SetXMMRegToNaN(viewType, &fstate.__fpu_xmm5); break; + case X86Encoding::xmm6: SetXMMRegToNaN(viewType, &fstate.__fpu_xmm6); break; + case X86Encoding::xmm7: SetXMMRegToNaN(viewType, &fstate.__fpu_xmm7); break; + case X86Encoding::xmm8: SetXMMRegToNaN(viewType, &fstate.__fpu_xmm8); break; + case X86Encoding::xmm9: SetXMMRegToNaN(viewType, &fstate.__fpu_xmm9); break; + case X86Encoding::xmm10: SetXMMRegToNaN(viewType, &fstate.__fpu_xmm10); break; + case X86Encoding::xmm11: SetXMMRegToNaN(viewType, &fstate.__fpu_xmm11); break; + case X86Encoding::xmm12: SetXMMRegToNaN(viewType, &fstate.__fpu_xmm12); break; + case X86Encoding::xmm13: SetXMMRegToNaN(viewType, &fstate.__fpu_xmm13); break; + case X86Encoding::xmm14: SetXMMRegToNaN(viewType, &fstate.__fpu_xmm14); break; + case X86Encoding::xmm15: SetXMMRegToNaN(viewType, &fstate.__fpu_xmm15); break; default: MOZ_CRASH(); } @@ -575,22 +575,22 @@ SetRegisterToCoercedUndefined(mach_port_t rtThread, x86_thread_state64_t &state, return false; } else { switch (heapAccess.loadedReg().gpr().code()) { - case X86Registers::eax: state.__rax = 0; break; - case X86Registers::ecx: state.__rcx = 0; break; - case X86Registers::edx: state.__rdx = 0; break; - case X86Registers::ebx: state.__rbx = 0; break; - case X86Registers::esp: state.__rsp = 0; break; - case X86Registers::ebp: state.__rbp = 0; break; - case X86Registers::esi: state.__rsi = 0; break; - case X86Registers::edi: state.__rdi = 0; break; - case X86Registers::r8: state.__r8 = 0; break; - case X86Registers::r9: state.__r9 = 0; break; - case X86Registers::r10: state.__r10 = 0; break; - case X86Registers::r11: state.__r11 = 0; break; - case X86Registers::r12: state.__r12 = 0; break; - case X86Registers::r13: state.__r13 = 0; break; - case X86Registers::r14: state.__r14 = 0; break; - case X86Registers::r15: state.__r15 = 0; break; + case X86Encoding::rax: state.__rax = 0; break; + case X86Encoding::rcx: state.__rcx = 0; break; + case X86Encoding::rdx: state.__rdx = 0; break; + case X86Encoding::rbx: state.__rbx = 0; break; + case X86Encoding::rsp: state.__rsp = 0; break; + case X86Encoding::rbp: state.__rbp = 0; break; + case X86Encoding::rsi: state.__rsi = 0; break; + case X86Encoding::rdi: state.__rdi = 0; break; + case X86Encoding::r8: state.__r8 = 0; break; + case X86Encoding::r9: state.__r9 = 0; break; + case X86Encoding::r10: state.__r10 = 0; break; + case X86Encoding::r11: state.__r11 = 0; break; + case X86Encoding::r12: state.__r12 = 0; break; + case X86Encoding::r13: state.__r13 = 0; break; + case X86Encoding::r14: state.__r14 = 0; break; + case X86Encoding::r15: state.__r15 = 0; break; default: MOZ_CRASH(); } } diff --git a/js/src/jit/shared/Assembler-x86-shared.cpp b/js/src/jit/shared/Assembler-x86-shared.cpp index 9908ed1084c..71c6ca5cd05 100644 --- a/js/src/jit/shared/Assembler-x86-shared.cpp +++ b/js/src/jit/shared/Assembler-x86-shared.cpp @@ -50,7 +50,7 @@ TraceDataRelocations(JSTracer *trc, uint8_t *buffer, CompactBufferReader &reader { while (reader.more()) { size_t offset = reader.readUnsigned(); - void **ptr = X86Assembler::getPointerRef(buffer + offset); + void **ptr = X86Encoding::GetPointerRef(buffer + offset); #ifdef JS_PUNBOX64 // All pointers on x64 will have the top bits cleared. If those bits diff --git a/js/src/jit/shared/Assembler-x86-shared.h b/js/src/jit/shared/Assembler-x86-shared.h index a851a21735c..b150b5e800b 100644 --- a/js/src/jit/shared/Assembler-x86-shared.h +++ b/js/src/jit/shared/Assembler-x86-shared.h @@ -68,7 +68,7 @@ class Operand { } explicit Operand(AbsoluteAddress address) : kind_(MEM_ADDRESS32), - disp_(X86Assembler::addressImmediate(address.addr)) + disp_(X86Encoding::AddressImmediate(address.addr)) { } Address toAddress() const { @@ -216,10 +216,10 @@ class AssemblerX86Shared : public AssemblerShared } protected: - X86Assembler masm; + X86Encoding::BaseAssembler masm; - typedef X86Assembler::JmpSrc JmpSrc; - typedef X86Assembler::JmpDst JmpDst; + typedef X86Encoding::JmpSrc JmpSrc; + typedef X86Encoding::JmpDst JmpDst; public: AssemblerX86Shared() @@ -229,23 +229,23 @@ class AssemblerX86Shared : public AssemblerShared } enum Condition { - Equal = X86Assembler::ConditionE, - NotEqual = X86Assembler::ConditionNE, - Above = X86Assembler::ConditionA, - AboveOrEqual = X86Assembler::ConditionAE, - Below = X86Assembler::ConditionB, - BelowOrEqual = X86Assembler::ConditionBE, - GreaterThan = X86Assembler::ConditionG, - GreaterThanOrEqual = X86Assembler::ConditionGE, - LessThan = X86Assembler::ConditionL, - LessThanOrEqual = X86Assembler::ConditionLE, - Overflow = X86Assembler::ConditionO, - Signed = X86Assembler::ConditionS, - NotSigned = X86Assembler::ConditionNS, - Zero = X86Assembler::ConditionE, - NonZero = X86Assembler::ConditionNE, - Parity = X86Assembler::ConditionP, - NoParity = X86Assembler::ConditionNP + Equal = X86Encoding::ConditionE, + NotEqual = X86Encoding::ConditionNE, + Above = X86Encoding::ConditionA, + AboveOrEqual = X86Encoding::ConditionAE, + Below = X86Encoding::ConditionB, + BelowOrEqual = X86Encoding::ConditionBE, + GreaterThan = X86Encoding::ConditionG, + GreaterThanOrEqual = X86Encoding::ConditionGE, + LessThan = X86Encoding::ConditionL, + LessThanOrEqual = X86Encoding::ConditionLE, + Overflow = X86Encoding::ConditionO, + Signed = X86Encoding::ConditionS, + NotSigned = X86Encoding::ConditionNS, + Zero = X86Encoding::ConditionE, + NonZero = X86Encoding::ConditionNE, + Parity = X86Encoding::ConditionP, + NoParity = X86Encoding::ConditionNP }; // If this bit is set, the vucomisd operands have to be inverted. @@ -788,10 +788,10 @@ class AssemblerX86Shared : public AssemblerShared void jSrc(Condition cond, Label *label) { if (label->bound()) { // The jump can be immediately encoded to the correct destination. - masm.jCC_i(static_cast(cond), JmpDst(label->offset())); + masm.jCC_i(static_cast(cond), JmpDst(label->offset())); } else { // Thread the jump list through the unpatched jump targets. - JmpSrc j = masm.jCC(static_cast(cond)); + JmpSrc j = masm.jCC(static_cast(cond)); JmpSrc prev = JmpSrc(label->use(j.offset())); masm.setNextJump(j, prev); } @@ -823,7 +823,7 @@ class AssemblerX86Shared : public AssemblerShared } JmpSrc jSrc(Condition cond, RepatchLabel *label) { - JmpSrc j = masm.jCC(static_cast(cond)); + JmpSrc j = masm.jCC(static_cast(cond)); if (label->bound()) { // The jump can be immediately patched to the correct destination. masm.linkJump(j, JmpDst(label->offset())); @@ -869,12 +869,12 @@ class AssemblerX86Shared : public AssemblerShared } void cmpEAX(Label *label) { cmpSrc(label); } void bind(Label *label) { - X86Assembler::JmpDst dst(masm.label()); + JmpDst dst(masm.label()); if (label->used()) { bool more; - X86Assembler::JmpSrc jmp(label->offset()); + JmpSrc jmp(label->offset()); do { - X86Assembler::JmpSrc next; + JmpSrc next; more = masm.nextJump(jmp, &next); masm.linkJump(jmp, dst); jmp = next; @@ -883,9 +883,9 @@ class AssemblerX86Shared : public AssemblerShared label->bind(dst.offset()); } void bind(RepatchLabel *label) { - X86Assembler::JmpDst dst(masm.label()); + JmpDst dst(masm.label()); if (label->used()) { - X86Assembler::JmpSrc jmp(label->offset()); + JmpSrc jmp(label->offset()); masm.linkJump(jmp, dst); } label->bind(dst.offset()); @@ -898,9 +898,9 @@ class AssemblerX86Shared : public AssemblerShared void retarget(Label *label, Label *target) { if (label->used()) { bool more; - X86Assembler::JmpSrc jmp(label->offset()); + JmpSrc jmp(label->offset()); do { - X86Assembler::JmpSrc next; + JmpSrc next; more = masm.nextJump(jmp, &next); if (target->bound()) { @@ -922,15 +922,15 @@ class AssemblerX86Shared : public AssemblerShared if (label->used()) { intptr_t src = label->offset(); do { - intptr_t next = reinterpret_cast(X86Assembler::getPointer(raw + src)); - X86Assembler::setPointer(raw + src, address); + intptr_t next = reinterpret_cast(X86Encoding::GetPointer(raw + src)); + X86Encoding::SetPointer(raw + src, address); src = next; } while (src != AbsoluteLabel::INVALID_OFFSET); } label->bind(); } - // See Bind and X86Assembler::setPointer. + // See Bind and X86Encoding::setPointer. size_t labelOffsetToPatchOffset(size_t offset) { return offset - sizeof(void*); } @@ -1040,7 +1040,7 @@ class AssemblerX86Shared : public AssemblerShared masm.cmpw_rr(rhs.code(), lhs.code()); } void setCC(Condition cond, Register r) { - masm.setCC_r(static_cast(cond), r.code()); + masm.setCC_r(static_cast(cond), r.code()); } void testb(Register rhs, Register lhs) { MOZ_ASSERT(GeneralRegisterSet(Registers::SingleByteRegs).has(rhs)); @@ -1694,19 +1694,19 @@ class AssemblerX86Shared : public AssemblerShared } } void vcmpeqps(const Operand &src1, FloatRegister src0, FloatRegister dest) { - vcmpps(X86Assembler::ConditionCmp_EQ, src1, src0, dest); + vcmpps(X86Encoding::ConditionCmp_EQ, src1, src0, dest); } void vcmpltps(const Operand &src1, FloatRegister src0, FloatRegister dest) { - vcmpps(X86Assembler::ConditionCmp_LT, src1, src0, dest); + vcmpps(X86Encoding::ConditionCmp_LT, src1, src0, dest); } void vcmpleps(const Operand &src1, FloatRegister src0, FloatRegister dest) { - vcmpps(X86Assembler::ConditionCmp_LE, src1, src0, dest); + vcmpps(X86Encoding::ConditionCmp_LE, src1, src0, dest); } void vcmpunordps(const Operand &src1, FloatRegister src0, FloatRegister dest) { - vcmpps(X86Assembler::ConditionCmp_UNORD, src1, src0, dest); + vcmpps(X86Encoding::ConditionCmp_UNORD, src1, src0, dest); } void vcmpneqps(const Operand &src1, FloatRegister src0, FloatRegister dest) { - vcmpps(X86Assembler::ConditionCmp_NEQ, src1, src0, dest); + vcmpps(X86Encoding::ConditionCmp_NEQ, src1, src0, dest); } void vrcpps(const Operand &src, FloatRegister dest) { MOZ_ASSERT(HasSSE2()); @@ -2333,11 +2333,11 @@ class AssemblerX86Shared : public AssemblerShared MOZ_ASSERT(HasSSE2()); masm.vsqrtss_rr(src1.code(), src0.code(), dest.code()); } - void vroundsd(X86Assembler::RoundingMode mode, FloatRegister src1, FloatRegister src0, FloatRegister dest) { + void vroundsd(X86Encoding::RoundingMode mode, FloatRegister src1, FloatRegister src0, FloatRegister dest) { MOZ_ASSERT(HasSSE41()); masm.vroundsd_irr(mode, src1.code(), src0.code(), dest.code()); } - void vroundss(X86Assembler::RoundingMode mode, FloatRegister src1, FloatRegister src0, FloatRegister dest) { + void vroundss(X86Encoding::RoundingMode mode, FloatRegister src1, FloatRegister src0, FloatRegister dest) { MOZ_ASSERT(HasSSE41()); masm.vroundss_irr(mode, src1.code(), src0.code(), dest.code()); } diff --git a/js/src/jit/shared/BaseAssembler-x86-shared.h b/js/src/jit/shared/BaseAssembler-x86-shared.h index 4be3804e450..2bcd89ca293 100644 --- a/js/src/jit/shared/BaseAssembler-x86-shared.h +++ b/js/src/jit/shared/BaseAssembler-x86-shared.h @@ -32,594 +32,23 @@ #include "mozilla/IntegerPrintfMacros.h" -#include - #include "jit/shared/AssemblerBuffer-x86-shared.h" - -#include "js/Vector.h" +#include "jit/shared/Encoding-x86-shared.h" +#include "jit/shared/Patching-x86-shared.h" namespace js { namespace jit { -inline bool CAN_SIGN_EXTEND_8_32(int32_t value) { return value == (int32_t)(int8_t)value; } -inline bool CAN_SIGN_EXTEND_16_32(int32_t value) { return value == (int32_t)(int16_t)value; } -inline bool CAN_ZERO_EXTEND_8_32(int32_t value) { return value == (int32_t)(uint8_t)value; } -inline bool CAN_ZERO_EXTEND_8H_32(int32_t value) { return value == (value & 0xff00); } -inline bool CAN_ZERO_EXTEND_16_32(int32_t value) { return value == (int32_t)(uint16_t)value; } -inline bool CAN_ZERO_EXTEND_32_64(int32_t value) { return value >= 0; } +namespace X86Encoding { -namespace X86Registers { - enum RegisterID { - eax, - ecx, - edx, - ebx, - esp, - ebp, - esi, - edi - -#ifdef JS_CODEGEN_X64 - ,r8, - r9, - r10, - r11, - r12, - r13, - r14, - r15 -#endif - ,invalid_reg - }; - - enum XMMRegisterID { - xmm0, - xmm1, - xmm2, - xmm3, - xmm4, - xmm5, - xmm6, - xmm7 -#ifdef JS_CODEGEN_X64 - ,xmm8, - xmm9, - xmm10, - xmm11, - xmm12, - xmm13, - xmm14, - xmm15 -#endif - ,invalid_xmm - }; - - static const char *XMMRegName(XMMRegisterID fpreg) - { - static const char *const names[] = { - "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7" -#ifdef JS_CODEGEN_X64 - , "%xmm8", "%xmm9", "%xmm10", "%xmm11", "%xmm12", "%xmm13", "%xmm14", "%xmm15" -#endif - }; - int off = fpreg - xmm0; - MOZ_ASSERT(off >= 0 && size_t(off) < mozilla::ArrayLength(names)); - return names[off]; - } - -#ifdef JS_CODEGEN_X64 - static const char *GPReg64Name(RegisterID reg) - { - static const char *const names[] = { - "%rax", "%rcx", "%rdx", "%rbx", "%rsp", "%rbp", "%rsi", "%rdi", - "%r8", "%r9", "%r10", "%r11", "%r12", "%r13", "%r14", "%r15" - }; - int off = reg - eax; - MOZ_ASSERT(off >= 0 && size_t(off) < mozilla::ArrayLength(names)); - return names[off]; - } -#endif - - static const char *GPReg32Name(RegisterID reg) - { - static const char *const names[] = { - "%eax", "%ecx", "%edx", "%ebx", "%esp", "%ebp", "%esi", "%edi" -#ifdef JS_CODEGEN_X64 - , "%r8d", "%r9d", "%r10d", "%r11d", "%r12d", "%r13d", "%r14d", "%r15d" -#endif - }; - int off = reg - eax; - MOZ_ASSERT(off >= 0 && size_t(off) < mozilla::ArrayLength(names)); - return names[off]; - } - - static const char *GPReg16Name(RegisterID reg) - { - static const char *const names[] = { - "%ax", "%cx", "%dx", "%bx", "%sp", "%bp", "%si", "%di" -#ifdef JS_CODEGEN_X64 - , "%r8w", "%r9w", "%r10w", "%r11w", "%r12w", "%r13w", "%r14w", "%r15w" -#endif - }; - int off = reg - eax; - MOZ_ASSERT(off >= 0 && size_t(off) < mozilla::ArrayLength(names)); - return names[off]; - } - - static const char *GPReg8Name(RegisterID reg) - { - // For %spl through %dil, assume we'll have a REX prefix. - static const char *const names[] = { - "%al", "%cl", "%dl", "%bl" -#ifdef JS_CODEGEN_X64 - , "%spl", "%bpl", "%sil", "%dil", - "%r8b", "%r9b", "%r10b", "%r11b", "%r12b", "%r13b", "%r14b", "%r15b" -#endif - }; - int off = reg - eax; - MOZ_ASSERT(off >= 0 && size_t(off) < mozilla::ArrayLength(names)); - return names[off]; - } - - // Like GPReg8Name, but emits non-REX names, when appropriate - static const char *GPReg8Name_norex(RegisterID reg) - { - // The same as names above, except that %spl through %dil are replaced - // by %ah through %bh since there is to be no REX prefix. - static const char *const names[] = { - "%al", "%cl", "%dl", "%bl", "%ah", "%ch", "%dh", "%bh" -#ifdef JS_CODEGEN_X64 - , "%r8b", "%r9b", "%r10b", "%r11b", "%r12b", "%r13b", "%r14b", "%r15b" -#endif - }; - int off = reg - eax; - MOZ_ASSERT(off >= 0 && size_t(off) < mozilla::ArrayLength(names)); - return names[off]; - } - - static const char *GPRegName(RegisterID reg) - { -#ifdef JS_CODEGEN_X64 - return GPReg64Name(reg); -#else - return GPReg32Name(reg); -#endif - } - - inline bool hasSubregL(RegisterID reg) - { -#ifdef JS_CODEGEN_X64 - // In 64-bit mode, all registers have an 8-bit lo subreg. - return true; -#else - // In 32-bit mode, only the first four registers do. - return reg <= ebx; -#endif - } - - inline bool hasSubregH(RegisterID reg) - { - // The first four registers always have h registers. However, note that - // on x64, h registers may not be used in instructions using REX - // prefixes. Also note that this may depend on what other registers are - // used! - return reg <= ebx; - } - - inline RegisterID getSubregH(RegisterID reg) - { - MOZ_ASSERT(hasSubregH(reg)); - return RegisterID(reg + 4); - } - - // Byte operand register spl & above require a REX prefix (to prevent - // the 'H' registers be accessed). - inline bool ByteRegRequiresRex(RegisterID reg) - { - return reg >= esp; - } -} /* namespace X86Registers */ - -class X86Assembler : public GenericAssembler { +class BaseAssembler : public GenericAssembler { public: - typedef X86Registers::RegisterID RegisterID; - typedef X86Registers::XMMRegisterID XMMRegisterID; - typedef XMMRegisterID FPRegisterID; - - enum Condition { - ConditionO, - ConditionNO, - ConditionB, - ConditionAE, - ConditionE, - ConditionNE, - ConditionBE, - ConditionA, - ConditionS, - ConditionNS, - ConditionP, - ConditionNP, - ConditionL, - ConditionGE, - ConditionLE, - ConditionG, - - ConditionC = ConditionB, - ConditionNC = ConditionAE - }; - - // Conditions for CMP instructions (CMPSS, CMPSD, CMPPS, CMPPD, etc). - enum ConditionCmp { - ConditionCmp_EQ = 0x0, - ConditionCmp_LT = 0x1, - ConditionCmp_LE = 0x2, - ConditionCmp_UNORD = 0x3, - ConditionCmp_NEQ = 0x4, - ConditionCmp_NLT = 0x5, - ConditionCmp_NLE = 0x6, - ConditionCmp_ORD = 0x7, - }; - - static const char* nameCC(Condition cc) - { - static const char* const names[16] - = { "o ", "no", "b ", "ae", "e ", "ne", "be", "a ", - "s ", "ns", "p ", "np", "l ", "ge", "le", "g " }; - int ix = (int)cc; - MOZ_ASSERT(ix >= 0 && size_t(ix) < mozilla::ArrayLength(names)); - return names[ix]; - } - - // Rounding modes for ROUNDSD. - enum RoundingMode { - RoundToNearest = 0x0, - RoundDown = 0x1, - RoundUp = 0x2, - RoundToZero = 0x3 - }; - -private: - enum OneByteOpcodeID { - OP_ADD_EbGb = 0x00, - OP_ADD_EvGv = 0x01, - OP_ADD_GvEv = 0x03, - OP_ADD_EAXIv = 0x05, - OP_OR_EbGb = 0x08, - OP_OR_EvGv = 0x09, - OP_OR_GvEv = 0x0B, - OP_OR_EAXIv = 0x0D, - OP_2BYTE_ESCAPE = 0x0F, - OP_AND_EbGb = 0x20, - OP_AND_EvGv = 0x21, - OP_AND_GvEv = 0x23, - OP_AND_EAXIv = 0x25, - OP_SUB_EbGb = 0x28, - OP_SUB_EvGv = 0x29, - OP_SUB_GvEv = 0x2B, - OP_SUB_EAXIv = 0x2D, - OP_XOR_EbGb = 0x30, - OP_XOR_EvGv = 0x31, - OP_XOR_GvEv = 0x33, - OP_XOR_EAXIv = 0x35, - OP_CMP_EvGv = 0x39, - OP_CMP_GvEv = 0x3B, - OP_CMP_EAXIv = 0x3D, -#ifdef JS_CODEGEN_X64 - PRE_REX = 0x40, -#endif - OP_PUSH_EAX = 0x50, - OP_POP_EAX = 0x58, -#ifdef JS_CODEGEN_X86 - OP_PUSHA = 0x60, - OP_POPA = 0x61, -#endif -#ifdef JS_CODEGEN_X64 - OP_MOVSXD_GvEv = 0x63, -#endif - PRE_OPERAND_SIZE = 0x66, - PRE_SSE_66 = 0x66, - OP_PUSH_Iz = 0x68, - OP_IMUL_GvEvIz = 0x69, - OP_PUSH_Ib = 0x6a, - OP_IMUL_GvEvIb = 0x6b, - OP_JCC_rel8 = 0x70, - OP_GROUP1_EbIb = 0x80, - OP_GROUP1_EvIz = 0x81, - OP_GROUP1_EvIb = 0x83, - OP_TEST_EbGb = 0x84, - OP_TEST_EvGv = 0x85, - OP_XCHG_GvEv = 0x87, - OP_MOV_EbGv = 0x88, - OP_MOV_EvGv = 0x89, - OP_MOV_GvEb = 0x8A, - OP_MOV_GvEv = 0x8B, - OP_LEA = 0x8D, - OP_GROUP1A_Ev = 0x8F, - OP_NOP = 0x90, - OP_PUSHFLAGS = 0x9C, - OP_POPFLAGS = 0x9D, - OP_CDQ = 0x99, - OP_MOV_EAXOv = 0xA1, - OP_MOV_OvEAX = 0xA3, - OP_TEST_EAXIb = 0xA8, - OP_TEST_EAXIv = 0xA9, - OP_MOV_EAXIv = 0xB8, - OP_GROUP2_EvIb = 0xC1, - OP_RET_Iz = 0xC2, - PRE_VEX_C4 = 0xC4, - PRE_VEX_C5 = 0xC5, - OP_RET = 0xC3, - OP_GROUP11_EvIb = 0xC6, - OP_GROUP11_EvIz = 0xC7, - OP_INT3 = 0xCC, - OP_GROUP2_Ev1 = 0xD1, - OP_GROUP2_EvCL = 0xD3, - OP_FPU6 = 0xDD, - OP_FPU6_F32 = 0xD9, - OP_CALL_rel32 = 0xE8, - OP_JMP_rel32 = 0xE9, - OP_JMP_rel8 = 0xEB, - PRE_LOCK = 0xF0, - PRE_SSE_F2 = 0xF2, - PRE_SSE_F3 = 0xF3, - OP_HLT = 0xF4, - OP_GROUP3_EbIb = 0xF6, - OP_GROUP3_Ev = 0xF7, - OP_GROUP3_EvIz = 0xF7, // OP_GROUP3_Ev has an immediate, when instruction is a test. - OP_GROUP5_Ev = 0xFF - }; - - enum ShiftID { - Shift_vpsrld = 2, - Shift_vpsrlq = 2, - Shift_vpsrldq = 3, - Shift_vpsrad = 4, - Shift_vpslld = 6, - Shift_vpsllq = 6 - }; - - enum TwoByteOpcodeID { - OP2_UD2 = 0x0B, - OP2_MOVSD_VsdWsd = 0x10, - OP2_MOVPS_VpsWps = 0x10, - OP2_MOVSD_WsdVsd = 0x11, - OP2_MOVPS_WpsVps = 0x11, - OP2_MOVHLPS_VqUq = 0x12, - OP2_MOVSLDUP_VpsWps = 0x12, - OP2_UNPCKLPS_VsdWsd = 0x14, - OP2_UNPCKHPS_VsdWsd = 0x15, - OP2_MOVLHPS_VqUq = 0x16, - OP2_MOVSHDUP_VpsWps = 0x16, - OP2_MOVAPD_VsdWsd = 0x28, - OP2_MOVAPS_VsdWsd = 0x28, - OP2_MOVAPS_WsdVsd = 0x29, - OP2_CVTSI2SD_VsdEd = 0x2A, - OP2_CVTTSD2SI_GdWsd = 0x2C, - OP2_UCOMISD_VsdWsd = 0x2E, - OP2_MOVMSKPD_EdVd = 0x50, - OP2_ANDPS_VpsWps = 0x54, - OP2_ANDNPS_VpsWps = 0x55, - OP2_ORPS_VpsWps = 0x56, - OP2_XORPS_VpsWps = 0x57, - OP2_ADDSD_VsdWsd = 0x58, - OP2_ADDPS_VpsWps = 0x58, - OP2_MULSD_VsdWsd = 0x59, - OP2_MULPS_VpsWps = 0x59, - OP2_CVTSS2SD_VsdEd = 0x5A, - OP2_CVTSD2SS_VsdEd = 0x5A, - OP2_CVTTPS2DQ_VdqWps = 0x5B, - OP2_CVTDQ2PS_VpsWdq = 0x5B, - OP2_SUBSD_VsdWsd = 0x5C, - OP2_SUBPS_VpsWps = 0x5C, - OP2_MINSD_VsdWsd = 0x5D, - OP2_MINSS_VssWss = 0x5D, - OP2_MINPS_VpsWps = 0x5D, - OP2_DIVSD_VsdWsd = 0x5E, - OP2_DIVPS_VpsWps = 0x5E, - OP2_MAXSD_VsdWsd = 0x5F, - OP2_MAXSS_VssWss = 0x5F, - OP2_MAXPS_VpsWps = 0x5F, - OP2_SQRTSD_VsdWsd = 0x51, - OP2_SQRTSS_VssWss = 0x51, - OP2_SQRTPS_VpsWps = 0x51, - OP2_RSQRTPS_VpsWps = 0x52, - OP2_RCPPS_VpsWps = 0x53, - OP2_ANDPD_VpdWpd = 0x54, - OP2_ORPD_VpdWpd = 0x56, - OP2_XORPD_VpdWpd = 0x57, - OP2_PCMPGTD_VdqWdq = 0x66, - OP2_MOVD_VdEd = 0x6E, - OP2_MOVDQ_VsdWsd = 0x6F, - OP2_MOVDQ_VdqWdq = 0x6F, - OP2_PSHUFD_VdqWdqIb = 0x70, - OP2_PSLLD_UdqIb = 0x72, - OP2_PSRAD_UdqIb = 0x72, - OP2_PSRLD_UdqIb = 0x72, - OP2_PSRLDQ_Vd = 0x73, - OP2_PCMPEQW = 0x75, - OP2_PCMPEQD_VdqWdq = 0x76, - OP2_MOVD_EdVd = 0x7E, - OP2_MOVDQ_WdqVdq = 0x7F, - OP2_JCC_rel32 = 0x80, - OP_SETCC = 0x90, - OP_FENCE = 0xAE, - OP2_IMUL_GvEv = 0xAF, - OP2_CMPXCHG_GvEb = 0xB0, - OP2_CMPXCHG_GvEw = 0xB1, - OP2_BSR_GvEv = 0xBD, - OP2_MOVSX_GvEb = 0xBE, - OP2_MOVSX_GvEw = 0xBF, - OP2_MOVZX_GvEb = 0xB6, - OP2_MOVZX_GvEw = 0xB7, - OP2_XADD_EbGb = 0xC0, - OP2_XADD_EvGv = 0xC1, - OP2_CMPPS_VpsWps = 0xC2, - OP2_PEXTRW_GdUdIb = 0xC5, - OP2_SHUFPS_VpsWpsIb = 0xC6, - OP2_PSRLD_VdqWdq = 0xD2, - OP2_PANDDQ_VdqWdq = 0xDB, - OP2_PANDNDQ_VdqWdq = 0xDF, - OP2_PSRAD_VdqWdq = 0xE2, - OP2_PORDQ_VdqWdq = 0xEB, - OP2_PXORDQ_VdqWdq = 0xEF, - OP2_PSLLD_VdqWdq = 0xF2, - OP2_PMULUDQ_VdqWdq = 0xF4, - OP2_PSUBD_VdqWdq = 0xFA, - OP2_PADDD_VdqWdq = 0xFE - }; - - // Test whether the given opcode should be printed with its operands reversed. - static bool IsXMMReversedOperands(TwoByteOpcodeID opcode) { - switch (opcode) { - case OP2_MOVSD_WsdVsd: // also OP2_MOVPS_WpsVps - case OP2_MOVAPS_WsdVsd: - case OP2_MOVDQ_WdqVdq: - case OP3_PEXTRD_EdVdqIb: - return true; - default: - break; - } - return false; - } - - enum ThreeByteOpcodeID { - OP3_ROUNDSS_VsdWsd = 0x0A, - OP3_ROUNDSD_VsdWsd = 0x0B, - OP3_BLENDVPS_VdqWdq = 0x14, - OP3_PEXTRD_EdVdqIb = 0x16, - OP3_BLENDPS_VpsWpsIb = 0x0C, - OP3_PTEST_VdVd = 0x17, - OP3_INSERTPS_VpsUps = 0x21, - OP3_PINSRD_VdqEdIb = 0x22, - OP3_PMULLD_VdqWdq = 0x40, - OP3_VBLENDVPS_VdqWdq = 0x4A - }; - - enum ThreeByteEscape { - ESCAPE_BLENDVPS = 0x38, - ESCAPE_PMULLD = 0x38, - ESCAPE_PTEST = 0x38, - ESCAPE_PINSRD = 0x3A, - ESCAPE_PEXTRD = 0x3A, - ESCAPE_ROUNDSD = 0x3A, - ESCAPE_INSERTPS = 0x3A, - ESCAPE_BLENDPS = 0x3A, - ESCAPE_VBLENDVPS = 0x3A - }; - - enum VexOperandType { - VEX_PS = 0, - VEX_PD = 1, - VEX_SS = 2, - VEX_SD = 3 - }; - - OneByteOpcodeID jccRel8(Condition cond) - { - return (OneByteOpcodeID)(OP_JCC_rel8 + cond); - } - TwoByteOpcodeID jccRel32(Condition cond) - { - return (TwoByteOpcodeID)(OP2_JCC_rel32 + cond); - } - - TwoByteOpcodeID setccOpcode(Condition cond) - { - return (TwoByteOpcodeID)(OP_SETCC + cond); - } - - enum GroupOpcodeID { - GROUP1_OP_ADD = 0, - GROUP1_OP_OR = 1, - GROUP1_OP_ADC = 2, - GROUP1_OP_AND = 4, - GROUP1_OP_SUB = 5, - GROUP1_OP_XOR = 6, - GROUP1_OP_CMP = 7, - - GROUP1A_OP_POP = 0, - - GROUP2_OP_SHL = 4, - GROUP2_OP_SHR = 5, - GROUP2_OP_SAR = 7, - - GROUP3_OP_TEST = 0, - GROUP3_OP_NOT = 2, - GROUP3_OP_NEG = 3, - GROUP3_OP_IMUL = 5, - GROUP3_OP_DIV = 6, - GROUP3_OP_IDIV = 7, - - GROUP5_OP_INC = 0, - GROUP5_OP_DEC = 1, - GROUP5_OP_CALLN = 2, - GROUP5_OP_JMPN = 4, - GROUP5_OP_PUSH = 6, - - FPU6_OP_FLD = 0, - FPU6_OP_FISTTP = 1, - FPU6_OP_FSTP = 3, - - GROUP11_MOV = 0 - }; - -public: - X86Assembler() + BaseAssembler() : useVEX_(true) { } void disableVEX() { useVEX_ = false; } - class JmpSrc { - public: - JmpSrc() - : m_offset(-1) - { - } - - explicit JmpSrc(int32_t offset) - : m_offset(offset) - { - } - - int32_t offset() const { - return m_offset; - } - - bool isSet() const { - return m_offset != -1; - } - - private: - int m_offset; - }; - - class JmpDst { - public: - JmpDst() - : m_offset(-1) - , m_used(false) - { - } - - bool isUsed() const { return m_used; } - void used() { m_used = true; } - bool isValid() const { return m_offset != -1; } - - explicit JmpDst(int32_t offset) - : m_offset(offset) - , m_used(false) - { - MOZ_ASSERT(m_offset == offset); - } - int32_t offset() const { - return m_offset; - } - private: - signed int m_offset : 31; - bool m_used : 1; - }; - size_t size() const { return m_formatter.size(); } const unsigned char *buffer() const { return m_formatter.buffer(); } unsigned char *data() { return m_formatter.data(); } @@ -736,7 +165,7 @@ public: m_formatter.oneByteOp(OP_GROUP1_EvIb, dst, GROUP1_OP_ADD); m_formatter.immediate8s(imm); } else { - if (dst == X86Registers::eax) + if (dst == rax) m_formatter.oneByteOp(OP_ADD_EAXIv); else m_formatter.oneByteOp(OP_GROUP1_EvIz, dst, GROUP1_OP_ADD); @@ -747,7 +176,7 @@ public: { // 32-bit immediate always, for patching. spew("addl $0x%04x, %s", imm, GPReg32Name(dst)); - if (dst == X86Registers::eax) + if (dst == rax) m_formatter.oneByteOp(OP_ADD_EAXIv); else m_formatter.oneByteOp(OP_GROUP1_EvIz, dst, GROUP1_OP_ADD); @@ -792,7 +221,7 @@ public: m_formatter.oneByteOp64(OP_GROUP1_EvIb, dst, GROUP1_OP_ADD); m_formatter.immediate8s(imm); } else { - if (dst == X86Registers::eax) + if (dst == rax) m_formatter.oneByteOp64(OP_ADD_EAXIv); else m_formatter.oneByteOp64(OP_GROUP1_EvIz, dst, GROUP1_OP_ADD); @@ -901,15 +330,15 @@ public: void vpmulld_rr(XMMRegisterID src1, XMMRegisterID src0, XMMRegisterID dst) { - threeByteOpSimd("vpmulld", VEX_PD, OP3_PMULLD_VdqWdq, ESCAPE_PMULLD, src1, src0, dst); + threeByteOpSimd("vpmulld", VEX_PD, OP3_PMULLD_VdqWdq, ESCAPE_38, src1, src0, dst); } void vpmulld_mr(int32_t offset, RegisterID base, XMMRegisterID src0, XMMRegisterID dst) { - threeByteOpSimd("vpmulld", VEX_PD, OP3_PMULLD_VdqWdq, ESCAPE_PMULLD, offset, base, src0, dst); + threeByteOpSimd("vpmulld", VEX_PD, OP3_PMULLD_VdqWdq, ESCAPE_38, offset, base, src0, dst); } void vpmulld_mr(const void* address, XMMRegisterID src0, XMMRegisterID dst) { - threeByteOpSimd("vpmulld", VEX_PD, OP3_PMULLD_VdqWdq, ESCAPE_PMULLD, address, src0, dst); + threeByteOpSimd("vpmulld", VEX_PD, OP3_PMULLD_VdqWdq, ESCAPE_38, address, src0, dst); } void vaddps_rr(XMMRegisterID src1, XMMRegisterID src0, XMMRegisterID dst) @@ -1015,7 +444,7 @@ public: m_formatter.oneByteOp(OP_GROUP1_EvIb, dst, GROUP1_OP_AND); m_formatter.immediate8s(imm); } else { - if (dst == X86Registers::eax) + if (dst == rax) m_formatter.oneByteOp(OP_AND_EAXIv); else m_formatter.oneByteOp(OP_GROUP1_EvIz, dst, GROUP1_OP_AND); @@ -1079,7 +508,7 @@ public: m_formatter.oneByteOp64(OP_GROUP1_EvIb, dst, GROUP1_OP_AND); m_formatter.immediate8s(imm); } else { - if (dst == X86Registers::eax) + if (dst == rax) m_formatter.oneByteOp64(OP_AND_EAXIv); else m_formatter.oneByteOp64(OP_GROUP1_EvIz, dst, GROUP1_OP_AND); @@ -1175,7 +604,7 @@ public: m_formatter.oneByteOp(OP_GROUP1_EvIb, dst, GROUP1_OP_OR); m_formatter.immediate8s(imm); } else { - if (dst == X86Registers::eax) + if (dst == rax) m_formatter.oneByteOp(OP_OR_EAXIv); else m_formatter.oneByteOp(OP_GROUP1_EvIz, dst, GROUP1_OP_OR); @@ -1215,7 +644,7 @@ public: m_formatter.oneByteOp64(OP_GROUP1_EvIb, dst, GROUP1_OP_OR); m_formatter.immediate8s(imm); } else { - if (dst == X86Registers::eax) + if (dst == rax) m_formatter.oneByteOp64(OP_OR_EAXIv); else m_formatter.oneByteOp64(OP_GROUP1_EvIz, dst, GROUP1_OP_OR); @@ -1267,7 +696,7 @@ public: m_formatter.oneByteOp(OP_GROUP1_EvIb, dst, GROUP1_OP_SUB); m_formatter.immediate8s(imm); } else { - if (dst == X86Registers::eax) + if (dst == rax) m_formatter.oneByteOp(OP_SUB_EAXIv); else m_formatter.oneByteOp(OP_GROUP1_EvIz, dst, GROUP1_OP_SUB); @@ -1319,7 +748,7 @@ public: m_formatter.oneByteOp64(OP_GROUP1_EvIb, dst, GROUP1_OP_SUB); m_formatter.immediate8s(imm); } else { - if (dst == X86Registers::eax) + if (dst == rax) m_formatter.oneByteOp64(OP_SUB_EAXIv); else m_formatter.oneByteOp64(OP_GROUP1_EvIz, dst, GROUP1_OP_SUB); @@ -1377,7 +806,7 @@ public: m_formatter.oneByteOp(OP_GROUP1_EvIb, dst, GROUP1_OP_XOR); m_formatter.immediate8s(imm); } else { - if (dst == X86Registers::eax) + if (dst == rax) m_formatter.oneByteOp(OP_XOR_EAXIv); else m_formatter.oneByteOp(OP_GROUP1_EvIz, dst, GROUP1_OP_XOR); @@ -1399,7 +828,7 @@ public: m_formatter.oneByteOp64(OP_GROUP1_EvIb, dst, GROUP1_OP_XOR); m_formatter.immediate8s(imm); } else { - if (dst == X86Registers::eax) + if (dst == rax) m_formatter.oneByteOp64(OP_XOR_EAXIv); else m_formatter.oneByteOp64(OP_GROUP1_EvIz, dst, GROUP1_OP_XOR); @@ -1654,7 +1083,7 @@ public: m_formatter.oneByteOp(OP_GROUP1_EvIb, lhs, GROUP1_OP_CMP); m_formatter.immediate8s(rhs); } else { - if (lhs == X86Registers::eax) + if (lhs == rax) m_formatter.oneByteOp(OP_CMP_EAXIv); else m_formatter.oneByteOp(OP_GROUP1_EvIz, lhs, GROUP1_OP_CMP); @@ -1665,7 +1094,7 @@ public: void cmpl_i32r(int32_t rhs, RegisterID lhs) { spew("cmpl $0x%04x, %s", rhs, GPReg32Name(lhs)); - if (lhs == X86Registers::eax) + if (lhs == rax) m_formatter.oneByteOp(OP_CMP_EAXIv); else m_formatter.oneByteOp(OP_GROUP1_EvIz, lhs, GROUP1_OP_CMP); @@ -1789,7 +1218,7 @@ public: m_formatter.oneByteOp64(OP_GROUP1_EvIb, lhs, GROUP1_OP_CMP); m_formatter.immediate8s(rhs); } else { - if (lhs == X86Registers::eax) + if (lhs == rax) m_formatter.oneByteOp64(OP_CMP_EAXIv); else m_formatter.oneByteOp64(OP_GROUP1_EvIz, lhs, GROUP1_OP_CMP); @@ -1905,18 +1334,18 @@ public: { // If the mask fits in an 8-bit immediate, we can use testb with an // 8-bit subreg. - if (CAN_ZERO_EXTEND_8_32(rhs) && X86Registers::hasSubregL(lhs)) { + if (CAN_ZERO_EXTEND_8_32(rhs) && HasSubregL(lhs)) { testb_ir(rhs, lhs); return; } // If the mask is a subset of 0xff00, we can use testb with an h reg, if // one happens to be available. - if (CAN_ZERO_EXTEND_8H_32(rhs) && X86Registers::hasSubregH(lhs)) { - testb_ir_norex(rhs >> 8, X86Registers::getSubregH(lhs)); + if (CAN_ZERO_EXTEND_8H_32(rhs) && HasSubregH(lhs)) { + testb_ir_norex(rhs >> 8, GetSubregH(lhs)); return; } spew("testl $0x%x, %s", rhs, GPReg32Name(lhs)); - if (lhs == X86Registers::eax) + if (lhs == rax) m_formatter.oneByteOp(OP_TEST_EAXIv); else m_formatter.oneByteOp(OP_GROUP3_EvIz, lhs, GROUP3_OP_TEST); @@ -1974,7 +1403,7 @@ public: return; } spew("testq $0x%" PRIx64 ", %s", int64_t(rhs), GPReg64Name(lhs)); - if (lhs == X86Registers::eax) + if (lhs == rax) m_formatter.oneByteOp64(OP_TEST_EAXIv); else m_formatter.oneByteOp64(OP_GROUP3_EvIz, lhs, GROUP3_OP_TEST); @@ -2006,7 +1435,7 @@ public: void testb_ir(int32_t rhs, RegisterID lhs) { spew("testb $0x%x, %s", rhs, GPReg8Name(lhs)); - if (lhs == X86Registers::eax) + if (lhs == rax) m_formatter.oneByteOp8(OP_TEST_EAXIb); else m_formatter.oneByteOp8(OP_GROUP3_EbIb, lhs, GROUP3_OP_TEST); @@ -2015,16 +1444,16 @@ public: // Like testb_ir, but never emits a REX prefix. This may be used to // reference ah..bh. - void testb_ir_norex(int32_t rhs, RegisterID lhs) + void testb_ir_norex(int32_t rhs, HRegisterID lhs) { - spew("testb $0x%x, %s", rhs, GPReg8Name_norex(lhs)); + spew("testb $0x%x, %s", rhs, HRegName8(lhs)); m_formatter.oneByteOp8_norex(OP_GROUP3_EbIb, lhs, GROUP3_OP_TEST); m_formatter.immediate8(rhs); } void setCC_r(Condition cond, RegisterID lhs) { - spew("set%s %s", nameCC(cond), GPReg8Name(lhs)); + spew("set%s %s", CCName(cond), GPReg8Name(lhs)); m_formatter.twoByteOp8(setccOpcode(cond), lhs, (GroupOpcodeID)0); } @@ -2125,8 +1554,8 @@ public: void movl_mEAX(const void* addr) { #ifdef JS_CODEGEN_X64 - if (isAddressImmediate(addr)) { - movl_mr(addr, X86Registers::eax); + if (IsAddressImmediate(addr)) { + movl_mr(addr, rax); return; } #endif @@ -2158,7 +1587,7 @@ public: void movl_mr(const void* base, RegisterID index, int scale, RegisterID dst) { - int32_t disp = addressImmediate(base); + int32_t disp = AddressImmediate(base); spew("movl " MEM_os ", %s", ADDR_os(disp, index, scale), GPReg32Name(dst)); m_formatter.oneByteOp_disp32(OP_MOV_GvEv, disp, index, scale, dst); @@ -2172,9 +1601,9 @@ public: void movl_mr(const void* addr, RegisterID dst) { - if (dst == X86Registers::eax + if (dst == rax #ifdef JS_CODEGEN_X64 - && !isAddressImmediate(addr) + && !IsAddressImmediate(addr) #endif ) { @@ -2262,8 +1691,8 @@ public: void movl_EAXm(const void* addr) { #ifdef JS_CODEGEN_X64 - if (isAddressImmediate(addr)) { - movl_rm(X86Registers::eax, addr); + if (IsAddressImmediate(addr)) { + movl_rm(rax, addr); return; } #endif @@ -2304,7 +1733,7 @@ public: void movq_rm(RegisterID src, const void* addr) { - if (src == X86Registers::eax && !isAddressImmediate(addr)) { + if (src == rax && !IsAddressImmediate(addr)) { movq_EAXm(addr); return; } @@ -2315,8 +1744,8 @@ public: void movq_mEAX(const void* addr) { - if (isAddressImmediate(addr)) { - movq_mr(addr, X86Registers::eax); + if (IsAddressImmediate(addr)) { + movq_mr(addr, rax); return; } @@ -2327,8 +1756,8 @@ public: void movq_EAXm(const void* addr) { - if (isAddressImmediate(addr)) { - movq_rm(X86Registers::eax, addr); + if (IsAddressImmediate(addr)) { + movq_rm(rax, addr); return; } @@ -2357,7 +1786,7 @@ public: void movq_mr(const void* addr, RegisterID dst) { - if (dst == X86Registers::eax && !isAddressImmediate(addr)) { + if (dst == rax && !IsAddressImmediate(addr)) { movq_mEAX(addr); return; } @@ -2446,9 +1875,9 @@ public: #endif void movl_rm(RegisterID src, const void* addr) { - if (src == X86Registers::eax + if (src == rax #ifdef JS_CODEGEN_X64 - && !isAddressImmediate(addr) + && !IsAddressImmediate(addr) #endif ) { movl_EAXm(addr); @@ -2744,7 +2173,7 @@ public: void jCC_i(Condition cond, JmpDst dst) { int32_t diff = dst.offset() - m_formatter.size(); - spew("j%s .Llabel%d", nameCC(cond), dst.offset()); + spew("j%s .Llabel%d", CCName(cond), dst.offset()); // The jump immediate is an offset from the end of the jump instruction. // A conditional jump instruction is either 1 byte opcode and 1 byte @@ -2763,7 +2192,7 @@ public: { m_formatter.twoByteOp(jccRel32(cond)); JmpSrc r = m_formatter.immediateRel32(); - spew("j%s .Lfrom%d", nameCC(cond), r.offset()); + spew("j%s .Lfrom%d", CCName(cond), r.offset()); return r; } @@ -2814,33 +2243,33 @@ public: } void vrcpps_rr(XMMRegisterID src, XMMRegisterID dst) { - twoByteOpSimd("vrcpps", VEX_PS, OP2_RCPPS_VpsWps, src, X86Registers::invalid_xmm, dst); + twoByteOpSimd("vrcpps", VEX_PS, OP2_RCPPS_VpsWps, src, invalid_xmm, dst); } void vrcpps_mr(int32_t offset, RegisterID base, XMMRegisterID dst) { - twoByteOpSimd("vrcpps", VEX_PS, OP2_RCPPS_VpsWps, offset, base, X86Registers::invalid_xmm, dst); + twoByteOpSimd("vrcpps", VEX_PS, OP2_RCPPS_VpsWps, offset, base, invalid_xmm, dst); } void vrcpps_mr(const void* address, XMMRegisterID dst) { - twoByteOpSimd("vrcpps", VEX_PS, OP2_RCPPS_VpsWps, address, X86Registers::invalid_xmm, dst); + twoByteOpSimd("vrcpps", VEX_PS, OP2_RCPPS_VpsWps, address, invalid_xmm, dst); } void vrsqrtps_rr(XMMRegisterID src, XMMRegisterID dst) { - twoByteOpSimd("vrsqrtps", VEX_PS, OP2_RSQRTPS_VpsWps, src, X86Registers::invalid_xmm, dst); + twoByteOpSimd("vrsqrtps", VEX_PS, OP2_RSQRTPS_VpsWps, src, invalid_xmm, dst); } void vrsqrtps_mr(int32_t offset, RegisterID base, XMMRegisterID dst) { - twoByteOpSimd("vrsqrtps", VEX_PS, OP2_RSQRTPS_VpsWps, offset, base, X86Registers::invalid_xmm, dst); + twoByteOpSimd("vrsqrtps", VEX_PS, OP2_RSQRTPS_VpsWps, offset, base, invalid_xmm, dst); } void vrsqrtps_mr(const void* address, XMMRegisterID dst) { - twoByteOpSimd("vrsqrtps", VEX_PS, OP2_RSQRTPS_VpsWps, address, X86Registers::invalid_xmm, dst); + twoByteOpSimd("vrsqrtps", VEX_PS, OP2_RSQRTPS_VpsWps, address, invalid_xmm, dst); } void vsqrtps_rr(XMMRegisterID src, XMMRegisterID dst) { - twoByteOpSimd("vsqrtps", VEX_PS, OP2_SQRTPS_VpsWps, src, X86Registers::invalid_xmm, dst); + twoByteOpSimd("vsqrtps", VEX_PS, OP2_SQRTPS_VpsWps, src, invalid_xmm, dst); } void vsqrtps_mr(int32_t offset, RegisterID base, XMMRegisterID dst) { - twoByteOpSimd("vsqrtps", VEX_PS, OP2_SQRTPS_VpsWps, offset, base, X86Registers::invalid_xmm, dst); + twoByteOpSimd("vsqrtps", VEX_PS, OP2_SQRTPS_VpsWps, offset, base, invalid_xmm, dst); } void vsqrtps_mr(const void* address, XMMRegisterID dst) { - twoByteOpSimd("vsqrtps", VEX_PS, OP2_SQRTPS_VpsWps, address, X86Registers::invalid_xmm, dst); + twoByteOpSimd("vsqrtps", VEX_PS, OP2_SQRTPS_VpsWps, address, invalid_xmm, dst); } void vaddsd_rr(XMMRegisterID src1, XMMRegisterID src0, XMMRegisterID dst) @@ -2894,12 +2323,12 @@ public: void vcvttps2dq_rr(XMMRegisterID src, XMMRegisterID dst) { - twoByteOpSimd("vcvttps2dq", VEX_SS, OP2_CVTTPS2DQ_VdqWps, src, X86Registers::invalid_xmm, dst); + twoByteOpSimd("vcvttps2dq", VEX_SS, OP2_CVTTPS2DQ_VdqWps, src, invalid_xmm, dst); } void vcvtdq2ps_rr(XMMRegisterID src, XMMRegisterID dst) { - twoByteOpSimd("vcvtdq2ps", VEX_PS, OP2_CVTDQ2PS_VpsWdq, src, X86Registers::invalid_xmm, dst); + twoByteOpSimd("vcvtdq2ps", VEX_PS, OP2_CVTDQ2PS_VpsWdq, src, invalid_xmm, dst); } #ifdef JS_CODEGEN_X64 @@ -3039,15 +2468,15 @@ public: void vpshufd_irr(uint32_t mask, XMMRegisterID src, XMMRegisterID dst) { - twoByteOpImmSimd("vpshufd", VEX_PD, OP2_PSHUFD_VdqWdqIb, mask, src, X86Registers::invalid_xmm, dst); + twoByteOpImmSimd("vpshufd", VEX_PD, OP2_PSHUFD_VdqWdqIb, mask, src, invalid_xmm, dst); } void vpshufd_imr(uint32_t mask, int32_t offset, RegisterID base, XMMRegisterID dst) { - twoByteOpImmSimd("vpshufd", VEX_PD, OP2_PSHUFD_VdqWdqIb, mask, offset, base, X86Registers::invalid_xmm, dst); + twoByteOpImmSimd("vpshufd", VEX_PD, OP2_PSHUFD_VdqWdqIb, mask, offset, base, invalid_xmm, dst); } void vpshufd_imr(uint32_t mask, const void* address, XMMRegisterID dst) { - twoByteOpImmSimd("vpshufd", VEX_PD, OP2_PSHUFD_VdqWdqIb, mask, address, X86Registers::invalid_xmm, dst); + twoByteOpImmSimd("vpshufd", VEX_PD, OP2_PSHUFD_VdqWdqIb, mask, address, invalid_xmm, dst); } void vshufps_irr(uint32_t mask, XMMRegisterID src1, XMMRegisterID src0, XMMRegisterID dst) @@ -3076,19 +2505,19 @@ public: void vpsrldq_ir(uint32_t count, XMMRegisterID src, XMMRegisterID dst) { MOZ_ASSERT(count < 16); - shiftOpImmSimd("vpsrldq", OP2_PSRLDQ_Vd, Shift_vpsrldq, count, src, dst); + shiftOpImmSimd("vpsrldq", OP2_PSRLDQ_Vd, ShiftID::vpsrldq, count, src, dst); } void vpsllq_ir(uint32_t count, XMMRegisterID src, XMMRegisterID dst) { MOZ_ASSERT(count < 64); - shiftOpImmSimd("vpsllq", OP2_PSRLDQ_Vd, Shift_vpsllq, count, src, dst); + shiftOpImmSimd("vpsllq", OP2_PSRLDQ_Vd, ShiftID::vpsllq, count, src, dst); } void vpsrlq_ir(uint32_t count, XMMRegisterID src, XMMRegisterID dst) { MOZ_ASSERT(count < 64); - shiftOpImmSimd("vpsrlq", OP2_PSRLDQ_Vd, Shift_vpsrlq, count, src, dst); + shiftOpImmSimd("vpsrlq", OP2_PSRLDQ_Vd, ShiftID::vpsrlq, count, src, dst); } void vpslld_rr(XMMRegisterID src1, XMMRegisterID src0, XMMRegisterID dst) @@ -3099,7 +2528,7 @@ public: void vpslld_ir(uint32_t count, XMMRegisterID src, XMMRegisterID dst) { MOZ_ASSERT(count < 32); - shiftOpImmSimd("vpslld", OP2_PSLLD_UdqIb, Shift_vpslld, count, src, dst); + shiftOpImmSimd("vpslld", OP2_PSLLD_UdqIb, ShiftID::vpslld, count, src, dst); } void vpsrad_rr(XMMRegisterID src1, XMMRegisterID src0, XMMRegisterID dst) @@ -3110,7 +2539,7 @@ public: void vpsrad_ir(int32_t count, XMMRegisterID src, XMMRegisterID dst) { MOZ_ASSERT(count < 32); - shiftOpImmSimd("vpsrad", OP2_PSRAD_UdqIb, Shift_vpsrad, count, src, dst); + shiftOpImmSimd("vpsrad", OP2_PSRAD_UdqIb, ShiftID::vpsrad, count, src, dst); } void vpsrld_rr(XMMRegisterID src1, XMMRegisterID src0, XMMRegisterID dst) @@ -3121,7 +2550,7 @@ public: void vpsrld_ir(uint32_t count, XMMRegisterID src, XMMRegisterID dst) { MOZ_ASSERT(count < 32); - shiftOpImmSimd("vpsrld", OP2_PSRLD_UdqIb, Shift_vpsrld, count, src, dst); + shiftOpImmSimd("vpsrld", OP2_PSRLD_UdqIb, ShiftID::vpsrld, count, src, dst); } void vmovmskpd_rr(XMMRegisterID src, RegisterID dst) @@ -3135,7 +2564,7 @@ public: } void vptest_rr(XMMRegisterID rhs, XMMRegisterID lhs) { - threeByteOpSimd("vptest", VEX_PD, OP3_PTEST_VdVd, ESCAPE_PTEST, rhs, X86Registers::invalid_xmm, lhs); + threeByteOpSimd("vptest", VEX_PD, OP3_PTEST_VdVd, ESCAPE_38, rhs, invalid_xmm, lhs); } void vmovd_rr(XMMRegisterID src, RegisterID dst) @@ -3145,7 +2574,7 @@ public: void vmovd_rr(RegisterID src, XMMRegisterID dst) { - twoByteOpInt32Simd("vmovd", VEX_PD, OP2_MOVD_VdEd, src, X86Registers::invalid_xmm, dst); + twoByteOpInt32Simd("vmovd", VEX_PD, OP2_MOVD_VdEd, src, invalid_xmm, dst); } #ifdef JS_CODEGEN_X64 @@ -3156,68 +2585,68 @@ public: void vmovq_rr(RegisterID src, XMMRegisterID dst) { - twoByteOpInt64Simd("vmovq", VEX_PD, OP2_MOVD_VdEd, src, X86Registers::invalid_xmm, dst); + twoByteOpInt64Simd("vmovq", VEX_PD, OP2_MOVD_VdEd, src, invalid_xmm, dst); } #endif void vmovsd_rm(XMMRegisterID src, int32_t offset, RegisterID base) { - twoByteOpSimd("vmovsd", VEX_SD, OP2_MOVSD_WsdVsd, offset, base, X86Registers::invalid_xmm, src); + twoByteOpSimd("vmovsd", VEX_SD, OP2_MOVSD_WsdVsd, offset, base, invalid_xmm, src); } void vmovsd_rm_disp32(XMMRegisterID src, int32_t offset, RegisterID base) { - twoByteOpSimd_disp32("vmovsd", VEX_SD, OP2_MOVSD_WsdVsd, offset, base, X86Registers::invalid_xmm, src); + twoByteOpSimd_disp32("vmovsd", VEX_SD, OP2_MOVSD_WsdVsd, offset, base, invalid_xmm, src); } void vmovss_rm(XMMRegisterID src, int32_t offset, RegisterID base) { - twoByteOpSimd("vmovss", VEX_SS, OP2_MOVSD_WsdVsd, offset, base, X86Registers::invalid_xmm, src); + twoByteOpSimd("vmovss", VEX_SS, OP2_MOVSD_WsdVsd, offset, base, invalid_xmm, src); } void vmovss_rm_disp32(XMMRegisterID src, int32_t offset, RegisterID base) { - twoByteOpSimd_disp32("vmovss", VEX_SS, OP2_MOVSD_WsdVsd, offset, base, X86Registers::invalid_xmm, src); + twoByteOpSimd_disp32("vmovss", VEX_SS, OP2_MOVSD_WsdVsd, offset, base, invalid_xmm, src); } void vmovss_mr(int32_t offset, RegisterID base, XMMRegisterID dst) { - twoByteOpSimd("vmovss", VEX_SS, OP2_MOVSD_VsdWsd, offset, base, X86Registers::invalid_xmm, dst); + twoByteOpSimd("vmovss", VEX_SS, OP2_MOVSD_VsdWsd, offset, base, invalid_xmm, dst); } void vmovss_mr_disp32(int32_t offset, RegisterID base, XMMRegisterID dst) { - twoByteOpSimd_disp32("vmovss", VEX_SS, OP2_MOVSD_VsdWsd, offset, base, X86Registers::invalid_xmm, dst); + twoByteOpSimd_disp32("vmovss", VEX_SS, OP2_MOVSD_VsdWsd, offset, base, invalid_xmm, dst); } void vmovsd_rm(XMMRegisterID src, int32_t offset, RegisterID base, RegisterID index, int scale) { - twoByteOpSimd("vmovsd", VEX_SD, OP2_MOVSD_WsdVsd, offset, base, index, scale, X86Registers::invalid_xmm, src); + twoByteOpSimd("vmovsd", VEX_SD, OP2_MOVSD_WsdVsd, offset, base, index, scale, invalid_xmm, src); } void vmovss_rm(XMMRegisterID src, int32_t offset, RegisterID base, RegisterID index, int scale) { - twoByteOpSimd("vmovss", VEX_SS, OP2_MOVSD_WsdVsd, offset, base, index, scale, X86Registers::invalid_xmm, src); + twoByteOpSimd("vmovss", VEX_SS, OP2_MOVSD_WsdVsd, offset, base, index, scale, invalid_xmm, src); } void vmovss_mr(int32_t offset, RegisterID base, RegisterID index, int scale, XMMRegisterID dst) { - twoByteOpSimd("vmovss", VEX_SS, OP2_MOVSD_VsdWsd, offset, base, index, scale, X86Registers::invalid_xmm, dst); + twoByteOpSimd("vmovss", VEX_SS, OP2_MOVSD_VsdWsd, offset, base, index, scale, invalid_xmm, dst); } void vmovsd_mr(int32_t offset, RegisterID base, XMMRegisterID dst) { - twoByteOpSimd("vmovsd", VEX_SD, OP2_MOVSD_VsdWsd, offset, base, X86Registers::invalid_xmm, dst); + twoByteOpSimd("vmovsd", VEX_SD, OP2_MOVSD_VsdWsd, offset, base, invalid_xmm, dst); } void vmovsd_mr_disp32(int32_t offset, RegisterID base, XMMRegisterID dst) { - twoByteOpSimd_disp32("vmovsd", VEX_SD, OP2_MOVSD_VsdWsd, offset, base, X86Registers::invalid_xmm, dst); + twoByteOpSimd_disp32("vmovsd", VEX_SD, OP2_MOVSD_VsdWsd, offset, base, invalid_xmm, dst); } void vmovsd_mr(int32_t offset, RegisterID base, RegisterID index, int scale, XMMRegisterID dst) { - twoByteOpSimd("vmovsd", VEX_SD, OP2_MOVSD_VsdWsd, offset, base, index, scale, X86Registers::invalid_xmm, dst); + twoByteOpSimd("vmovsd", VEX_SD, OP2_MOVSD_VsdWsd, offset, base, index, scale, invalid_xmm, dst); } // Note that the register-to-register form of vmovsd does not write to the @@ -3237,83 +2666,83 @@ public: void vmovsd_mr(const void* address, XMMRegisterID dst) { - twoByteOpSimd("vmovsd", VEX_SD, OP2_MOVSD_VsdWsd, address, X86Registers::invalid_xmm, dst); + twoByteOpSimd("vmovsd", VEX_SD, OP2_MOVSD_VsdWsd, address, invalid_xmm, dst); } void vmovss_mr(const void* address, XMMRegisterID dst) { - twoByteOpSimd("vmovss", VEX_SS, OP2_MOVSD_VsdWsd, address, X86Registers::invalid_xmm, dst); + twoByteOpSimd("vmovss", VEX_SS, OP2_MOVSD_VsdWsd, address, invalid_xmm, dst); } void vmovups_mr(const void* address, XMMRegisterID dst) { - twoByteOpSimd("vmovups", VEX_PS, OP2_MOVPS_VpsWps, address, X86Registers::invalid_xmm, dst); + twoByteOpSimd("vmovups", VEX_PS, OP2_MOVPS_VpsWps, address, invalid_xmm, dst); } void vmovdqu_mr(const void* address, XMMRegisterID dst) { - twoByteOpSimd("vmovdqu", VEX_SS, OP2_MOVDQ_VdqWdq, address, X86Registers::invalid_xmm, dst); + twoByteOpSimd("vmovdqu", VEX_SS, OP2_MOVDQ_VdqWdq, address, invalid_xmm, dst); } void vmovsd_rm(XMMRegisterID src, const void* address) { - twoByteOpSimd("vmovsd", VEX_SD, OP2_MOVSD_WsdVsd, address, X86Registers::invalid_xmm, src); + twoByteOpSimd("vmovsd", VEX_SD, OP2_MOVSD_WsdVsd, address, invalid_xmm, src); } void vmovss_rm(XMMRegisterID src, const void* address) { - twoByteOpSimd("vmovss", VEX_SS, OP2_MOVSD_WsdVsd, address, X86Registers::invalid_xmm, src); + twoByteOpSimd("vmovss", VEX_SS, OP2_MOVSD_WsdVsd, address, invalid_xmm, src); } void vmovdqa_rm(XMMRegisterID src, const void* address) { - twoByteOpSimd("vmovdqa", VEX_PD, OP2_MOVDQ_WdqVdq, address, X86Registers::invalid_xmm, src); + twoByteOpSimd("vmovdqa", VEX_PD, OP2_MOVDQ_WdqVdq, address, invalid_xmm, src); } void vmovaps_rm(XMMRegisterID src, const void* address) { - twoByteOpSimd("vmovaps", VEX_PS, OP2_MOVAPS_WsdVsd, address, X86Registers::invalid_xmm, src); + twoByteOpSimd("vmovaps", VEX_PS, OP2_MOVAPS_WsdVsd, address, invalid_xmm, src); } void vmovdqu_rm(XMMRegisterID src, const void* address) { - twoByteOpSimd("vmovdqu", VEX_SS, OP2_MOVDQ_WdqVdq, address, X86Registers::invalid_xmm, src); + twoByteOpSimd("vmovdqu", VEX_SS, OP2_MOVDQ_WdqVdq, address, invalid_xmm, src); } void vmovups_rm(XMMRegisterID src, const void* address) { - twoByteOpSimd("vmovups", VEX_PS, OP2_MOVPS_WpsVps, address, X86Registers::invalid_xmm, src); + twoByteOpSimd("vmovups", VEX_PS, OP2_MOVPS_WpsVps, address, invalid_xmm, src); } #ifdef JS_CODEGEN_X64 MOZ_WARN_UNUSED_RESULT JmpSrc vmovsd_ripr(XMMRegisterID dst) { - return twoByteRipOpSimd("vmovsd", VEX_SD, OP2_MOVSD_VsdWsd, X86Registers::invalid_xmm, dst); + return twoByteRipOpSimd("vmovsd", VEX_SD, OP2_MOVSD_VsdWsd, invalid_xmm, dst); } MOZ_WARN_UNUSED_RESULT JmpSrc vmovss_ripr(XMMRegisterID dst) { - return twoByteRipOpSimd("vmovss", VEX_SS, OP2_MOVSD_VsdWsd, X86Registers::invalid_xmm, dst); + return twoByteRipOpSimd("vmovss", VEX_SS, OP2_MOVSD_VsdWsd, invalid_xmm, dst); } MOZ_WARN_UNUSED_RESULT JmpSrc vmovsd_rrip(XMMRegisterID src) { - return twoByteRipOpSimd("vmovsd", VEX_SD, OP2_MOVSD_WsdVsd, X86Registers::invalid_xmm, src); + return twoByteRipOpSimd("vmovsd", VEX_SD, OP2_MOVSD_WsdVsd, invalid_xmm, src); } MOZ_WARN_UNUSED_RESULT JmpSrc vmovss_rrip(XMMRegisterID src) { - return twoByteRipOpSimd("vmovss", VEX_SS, OP2_MOVSD_WsdVsd, X86Registers::invalid_xmm, src); + return twoByteRipOpSimd("vmovss", VEX_SS, OP2_MOVSD_WsdVsd, invalid_xmm, src); } MOZ_WARN_UNUSED_RESULT JmpSrc vmovdqa_rrip(XMMRegisterID src) { - return twoByteRipOpSimd("vmovdqa", VEX_PD, OP2_MOVDQ_WdqVdq, X86Registers::invalid_xmm, src); + return twoByteRipOpSimd("vmovdqa", VEX_PD, OP2_MOVDQ_WdqVdq, invalid_xmm, src); } MOZ_WARN_UNUSED_RESULT JmpSrc vmovaps_rrip(XMMRegisterID src) { - return twoByteRipOpSimd("vmovdqa", VEX_PS, OP2_MOVAPS_WsdVsd, X86Registers::invalid_xmm, src); + return twoByteRipOpSimd("vmovdqa", VEX_PS, OP2_MOVAPS_WsdVsd, invalid_xmm, src); } #endif @@ -3324,53 +2753,53 @@ public: // one register in [xmm8,xmm15] and one in [xmm0,xmm7], use the // opcode which swaps the operands, as that way we can get a two-byte // VEX in that case. - if (src >= X86Registers::xmm8 && dst < X86Registers::xmm8) { - twoByteOpSimd("vmovaps", VEX_PS, OP2_MOVAPS_WsdVsd, dst, X86Registers::invalid_xmm, src); + if (src >= xmm8 && dst < xmm8) { + twoByteOpSimd("vmovaps", VEX_PS, OP2_MOVAPS_WsdVsd, dst, invalid_xmm, src); return; } #endif - twoByteOpSimd("vmovaps", VEX_PS, OP2_MOVAPS_VsdWsd, src, X86Registers::invalid_xmm, dst); + twoByteOpSimd("vmovaps", VEX_PS, OP2_MOVAPS_VsdWsd, src, invalid_xmm, dst); } void vmovaps_rm(XMMRegisterID src, int32_t offset, RegisterID base) { - twoByteOpSimd("vmovaps", VEX_PS, OP2_MOVAPS_WsdVsd, offset, base, X86Registers::invalid_xmm, src); + twoByteOpSimd("vmovaps", VEX_PS, OP2_MOVAPS_WsdVsd, offset, base, invalid_xmm, src); } void vmovaps_rm(XMMRegisterID src, int32_t offset, RegisterID base, RegisterID index, int scale) { - twoByteOpSimd("vmovaps", VEX_PS, OP2_MOVAPS_WsdVsd, offset, base, index, scale, X86Registers::invalid_xmm, src); + twoByteOpSimd("vmovaps", VEX_PS, OP2_MOVAPS_WsdVsd, offset, base, index, scale, invalid_xmm, src); } void vmovaps_mr(int32_t offset, RegisterID base, XMMRegisterID dst) { - twoByteOpSimd("vmovaps", VEX_PS, OP2_MOVAPS_VsdWsd, offset, base, X86Registers::invalid_xmm, dst); + twoByteOpSimd("vmovaps", VEX_PS, OP2_MOVAPS_VsdWsd, offset, base, invalid_xmm, dst); } void vmovaps_mr(int32_t offset, RegisterID base, RegisterID index, int scale, XMMRegisterID dst) { - twoByteOpSimd("vmovaps", VEX_PS, OP2_MOVAPS_VsdWsd, offset, base, index, scale, X86Registers::invalid_xmm, dst); + twoByteOpSimd("vmovaps", VEX_PS, OP2_MOVAPS_VsdWsd, offset, base, index, scale, invalid_xmm, dst); } void vmovups_rm(XMMRegisterID src, int32_t offset, RegisterID base) { - twoByteOpSimd("vmovups", VEX_PS, OP2_MOVPS_WpsVps, offset, base, X86Registers::invalid_xmm, src); + twoByteOpSimd("vmovups", VEX_PS, OP2_MOVPS_WpsVps, offset, base, invalid_xmm, src); } void vmovups_rm_disp32(XMMRegisterID src, int32_t offset, RegisterID base) { - twoByteOpSimd_disp32("vmovups", VEX_PS, OP2_MOVPS_WpsVps, offset, base, X86Registers::invalid_xmm, src); + twoByteOpSimd_disp32("vmovups", VEX_PS, OP2_MOVPS_WpsVps, offset, base, invalid_xmm, src); } void vmovups_rm(XMMRegisterID src, int32_t offset, RegisterID base, RegisterID index, int scale) { - twoByteOpSimd("vmovups", VEX_PS, OP2_MOVPS_WpsVps, offset, base, index, scale, X86Registers::invalid_xmm, src); + twoByteOpSimd("vmovups", VEX_PS, OP2_MOVPS_WpsVps, offset, base, index, scale, invalid_xmm, src); } void vmovups_mr(int32_t offset, RegisterID base, XMMRegisterID dst) { - twoByteOpSimd("vmovups", VEX_PS, OP2_MOVPS_VpsWps, offset, base, X86Registers::invalid_xmm, dst); + twoByteOpSimd("vmovups", VEX_PS, OP2_MOVPS_VpsWps, offset, base, invalid_xmm, dst); } void vmovups_mr_disp32(int32_t offset, RegisterID base, XMMRegisterID dst) { - twoByteOpSimd_disp32("vmovups", VEX_PS, OP2_MOVPS_VpsWps, offset, base, X86Registers::invalid_xmm, dst); + twoByteOpSimd_disp32("vmovups", VEX_PS, OP2_MOVPS_VpsWps, offset, base, invalid_xmm, dst); } void vmovups_mr(int32_t offset, RegisterID base, RegisterID index, int scale, XMMRegisterID dst) { - twoByteOpSimd("vmovups", VEX_PS, OP2_MOVPS_VpsWps, offset, base, index, scale, X86Registers::invalid_xmm, dst); + twoByteOpSimd("vmovups", VEX_PS, OP2_MOVPS_VpsWps, offset, base, index, scale, invalid_xmm, dst); } void vmovapd_rr(XMMRegisterID src, XMMRegisterID dst) @@ -3380,66 +2809,66 @@ public: // one register in [xmm8,xmm15] and one in [xmm0,xmm7], use the // opcode which swaps the operands, as that way we can get a two-byte // VEX in that case. - if (src >= X86Registers::xmm8 && dst < X86Registers::xmm8) { - twoByteOpSimd("vmovapd", VEX_PD, OP2_MOVAPS_WsdVsd, dst, X86Registers::invalid_xmm, src); + if (src >= xmm8 && dst < xmm8) { + twoByteOpSimd("vmovapd", VEX_PD, OP2_MOVAPS_WsdVsd, dst, invalid_xmm, src); return; } #endif - twoByteOpSimd("vmovapd", VEX_PD, OP2_MOVAPD_VsdWsd, src, X86Registers::invalid_xmm, dst); + twoByteOpSimd("vmovapd", VEX_PD, OP2_MOVAPD_VsdWsd, src, invalid_xmm, dst); } #ifdef JS_CODEGEN_X64 MOZ_WARN_UNUSED_RESULT JmpSrc vmovaps_ripr(XMMRegisterID dst) { - return twoByteRipOpSimd("vmovaps", VEX_PS, OP2_MOVAPS_VsdWsd, X86Registers::invalid_xmm, dst); + return twoByteRipOpSimd("vmovaps", VEX_PS, OP2_MOVAPS_VsdWsd, invalid_xmm, dst); } MOZ_WARN_UNUSED_RESULT JmpSrc vmovdqa_ripr(XMMRegisterID dst) { - return twoByteRipOpSimd("vmovdqa", VEX_PD, OP2_MOVDQ_VdqWdq, X86Registers::invalid_xmm, dst); + return twoByteRipOpSimd("vmovdqa", VEX_PD, OP2_MOVDQ_VdqWdq, invalid_xmm, dst); } #else void vmovaps_mr(const void* address, XMMRegisterID dst) { - twoByteOpSimd("vmovaps", VEX_PS, OP2_MOVAPS_VsdWsd, address, X86Registers::invalid_xmm, dst); + twoByteOpSimd("vmovaps", VEX_PS, OP2_MOVAPS_VsdWsd, address, invalid_xmm, dst); } void vmovdqa_mr(const void* address, XMMRegisterID dst) { - twoByteOpSimd("vmovdqa", VEX_PD, OP2_MOVDQ_VdqWdq, address, X86Registers::invalid_xmm, dst); + twoByteOpSimd("vmovdqa", VEX_PD, OP2_MOVDQ_VdqWdq, address, invalid_xmm, dst); } #endif // JS_CODEGEN_X64 void vmovdqu_rm(XMMRegisterID src, int32_t offset, RegisterID base) { - twoByteOpSimd("vmovdqu", VEX_SS, OP2_MOVDQ_WdqVdq, offset, base, X86Registers::invalid_xmm, src); + twoByteOpSimd("vmovdqu", VEX_SS, OP2_MOVDQ_WdqVdq, offset, base, invalid_xmm, src); } void vmovdqu_rm_disp32(XMMRegisterID src, int32_t offset, RegisterID base) { - twoByteOpSimd_disp32("vmovdqu", VEX_SS, OP2_MOVDQ_WdqVdq, offset, base, X86Registers::invalid_xmm, src); + twoByteOpSimd_disp32("vmovdqu", VEX_SS, OP2_MOVDQ_WdqVdq, offset, base, invalid_xmm, src); } void vmovdqu_rm(XMMRegisterID src, int32_t offset, RegisterID base, RegisterID index, int scale) { - twoByteOpSimd("vmovdqu", VEX_SS, OP2_MOVDQ_WdqVdq, offset, base, index, scale, X86Registers::invalid_xmm, src); + twoByteOpSimd("vmovdqu", VEX_SS, OP2_MOVDQ_WdqVdq, offset, base, index, scale, invalid_xmm, src); } void vmovdqu_mr(int32_t offset, RegisterID base, XMMRegisterID dst) { - twoByteOpSimd("vmovdqu", VEX_SS, OP2_MOVDQ_VdqWdq, offset, base, X86Registers::invalid_xmm, dst); + twoByteOpSimd("vmovdqu", VEX_SS, OP2_MOVDQ_VdqWdq, offset, base, invalid_xmm, dst); } void vmovdqu_mr_disp32(int32_t offset, RegisterID base, XMMRegisterID dst) { - twoByteOpSimd_disp32("vmovdqu", VEX_SS, OP2_MOVDQ_VdqWdq, offset, base, X86Registers::invalid_xmm, dst); + twoByteOpSimd_disp32("vmovdqu", VEX_SS, OP2_MOVDQ_VdqWdq, offset, base, invalid_xmm, dst); } void vmovdqu_mr(int32_t offset, RegisterID base, RegisterID index, int scale, XMMRegisterID dst) { - twoByteOpSimd("vmovdqu", VEX_SS, OP2_MOVDQ_VdqWdq, offset, base, index, scale, X86Registers::invalid_xmm, dst); + twoByteOpSimd("vmovdqu", VEX_SS, OP2_MOVDQ_VdqWdq, offset, base, index, scale, invalid_xmm, dst); } void vmovdqa_rr(XMMRegisterID src, XMMRegisterID dst) @@ -3449,33 +2878,33 @@ public: // one register in [xmm8,xmm15] and one in [xmm0,xmm7], use the // opcode which swaps the operands, as that way we can get a two-byte // VEX in that case. - if (src >= X86Registers::xmm8 && dst < X86Registers::xmm8) { - twoByteOpSimd("vmovdqa", VEX_PD, OP2_MOVDQ_WdqVdq, dst, X86Registers::invalid_xmm, src); + if (src >= xmm8 && dst < xmm8) { + twoByteOpSimd("vmovdqa", VEX_PD, OP2_MOVDQ_WdqVdq, dst, invalid_xmm, src); return; } #endif - twoByteOpSimd("vmovdqa", VEX_PD, OP2_MOVDQ_VdqWdq, src, X86Registers::invalid_xmm, dst); + twoByteOpSimd("vmovdqa", VEX_PD, OP2_MOVDQ_VdqWdq, src, invalid_xmm, dst); } void vmovdqa_rm(XMMRegisterID src, int32_t offset, RegisterID base) { - twoByteOpSimd("vmovdqa", VEX_PD, OP2_MOVDQ_WdqVdq, offset, base, X86Registers::invalid_xmm, src); + twoByteOpSimd("vmovdqa", VEX_PD, OP2_MOVDQ_WdqVdq, offset, base, invalid_xmm, src); } void vmovdqa_rm(XMMRegisterID src, int32_t offset, RegisterID base, RegisterID index, int scale) { - twoByteOpSimd("vmovdqa", VEX_PD, OP2_MOVDQ_WdqVdq, offset, base, index, scale, X86Registers::invalid_xmm, src); + twoByteOpSimd("vmovdqa", VEX_PD, OP2_MOVDQ_WdqVdq, offset, base, index, scale, invalid_xmm, src); } void vmovdqa_mr(int32_t offset, RegisterID base, XMMRegisterID dst) { - twoByteOpSimd("vmovdqa", VEX_PD, OP2_MOVDQ_VdqWdq, offset, base, X86Registers::invalid_xmm, dst); + twoByteOpSimd("vmovdqa", VEX_PD, OP2_MOVDQ_VdqWdq, offset, base, invalid_xmm, dst); } void vmovdqa_mr(int32_t offset, RegisterID base, RegisterID index, int scale, XMMRegisterID dst) { - twoByteOpSimd("vmovdqa", VEX_PD, OP2_MOVDQ_VdqWdq, offset, base, index, scale, X86Registers::invalid_xmm, dst); + twoByteOpSimd("vmovdqa", VEX_PD, OP2_MOVDQ_VdqWdq, offset, base, index, scale, invalid_xmm, dst); } void vmulsd_rr(XMMRegisterID src1, XMMRegisterID src0, XMMRegisterID dst) @@ -3646,39 +3075,39 @@ public: void vroundsd_irr(RoundingMode mode, XMMRegisterID src1, XMMRegisterID src0, XMMRegisterID dst) { - threeByteOpImmSimd("vroundsd", VEX_PD, OP3_ROUNDSD_VsdWsd, ESCAPE_ROUNDSD, mode, src1, src0, dst); + threeByteOpImmSimd("vroundsd", VEX_PD, OP3_ROUNDSD_VsdWsd, ESCAPE_3A, mode, src1, src0, dst); } void vroundss_irr(RoundingMode mode, XMMRegisterID src1, XMMRegisterID src0, XMMRegisterID dst) { - threeByteOpImmSimd("vroundss", VEX_PD, OP3_ROUNDSS_VsdWsd, ESCAPE_ROUNDSD, mode, src1, src0, dst); + threeByteOpImmSimd("vroundss", VEX_PD, OP3_ROUNDSS_VsdWsd, ESCAPE_3A, mode, src1, src0, dst); } void vinsertps_irr(uint32_t mask, XMMRegisterID src1, XMMRegisterID src0, XMMRegisterID dst) { - threeByteOpImmSimd("vinsertps", VEX_PD, OP3_INSERTPS_VpsUps, ESCAPE_INSERTPS, mask, src1, src0, dst); + threeByteOpImmSimd("vinsertps", VEX_PD, OP3_INSERTPS_VpsUps, ESCAPE_3A, mask, src1, src0, dst); } void vinsertps_imr(uint32_t mask, int32_t offset, RegisterID base, XMMRegisterID src0, XMMRegisterID dst) { - threeByteOpImmSimd("vinsertps", VEX_PD, OP3_INSERTPS_VpsUps, ESCAPE_INSERTPS, mask, offset, base, src0, dst); + threeByteOpImmSimd("vinsertps", VEX_PD, OP3_INSERTPS_VpsUps, ESCAPE_3A, mask, offset, base, src0, dst); } void vpinsrd_irr(unsigned lane, RegisterID src1, XMMRegisterID src0, XMMRegisterID dst) { MOZ_ASSERT(lane < 4); - threeByteOpImmInt32Simd("vpinsrd", VEX_PD, OP3_PINSRD_VdqEdIb, ESCAPE_PINSRD, lane, src1, src0, dst); + threeByteOpImmInt32Simd("vpinsrd", VEX_PD, OP3_PINSRD_VdqEdIb, ESCAPE_3A, lane, src1, src0, dst); } void vpinsrd_imr(unsigned lane, int32_t offset, RegisterID base, XMMRegisterID src0, XMMRegisterID dst) { MOZ_ASSERT(lane < 4); - threeByteOpImmInt32Simd("vpinsrd", VEX_PD, OP3_PINSRD_VdqEdIb, ESCAPE_PINSRD, lane, offset, base, src0, dst); + threeByteOpImmInt32Simd("vpinsrd", VEX_PD, OP3_PINSRD_VdqEdIb, ESCAPE_3A, lane, offset, base, src0, dst); } void vpextrd_irr(unsigned lane, XMMRegisterID src, RegisterID dst) { MOZ_ASSERT(lane < 4); - threeByteOpImmSimdInt32("vpextrd", VEX_PD, OP3_PEXTRD_EdVdqIb, ESCAPE_PEXTRD, lane, (XMMRegisterID)dst, (RegisterID)src); + threeByteOpImmSimdInt32("vpextrd", VEX_PD, OP3_PEXTRD_EdVdqIb, ESCAPE_3A, lane, (XMMRegisterID)dst, (RegisterID)src); } void vpextrd_irm(unsigned lane, XMMRegisterID src, int32_t offset, RegisterID base) @@ -3686,7 +3115,7 @@ public: MOZ_ASSERT(lane < 4); spew("pextrd $0x%x, %s, " MEM_ob, lane, XMMRegName(src), ADDR_ob(offset, base)); m_formatter.prefix(PRE_SSE_66); - m_formatter.threeByteOp(OP3_PEXTRD_EdVdqIb, ESCAPE_PEXTRD, offset, base, (RegisterID)src); + m_formatter.threeByteOp(OP3_PEXTRD_EdVdqIb, ESCAPE_3A, offset, base, (RegisterID)src); m_formatter.immediate8u(lane); } @@ -3694,14 +3123,14 @@ public: { MOZ_ASSERT(imm < 16); // Despite being a "ps" instruction, vblendps is encoded with the "pd" prefix. - threeByteOpImmSimd("vblendps", VEX_PD, OP3_BLENDPS_VpsWpsIb, ESCAPE_BLENDPS, imm, src1, src0, dst); + threeByteOpImmSimd("vblendps", VEX_PD, OP3_BLENDPS_VpsWpsIb, ESCAPE_3A, imm, src1, src0, dst); } void vblendps_imr(unsigned imm, int32_t offset, RegisterID base, XMMRegisterID src0, XMMRegisterID dst) { MOZ_ASSERT(imm < 16); // Despite being a "ps" instruction, vblendps is encoded with the "pd" prefix. -threeByteOpImmSimd("vblendps", VEX_PD, OP3_BLENDPS_VpsWpsIb, ESCAPE_BLENDPS, imm, offset, base, src0, dst); +threeByteOpImmSimd("vblendps", VEX_PD, OP3_BLENDPS_VpsWpsIb, ESCAPE_3A, imm, offset, base, src0, dst); } void vblendvps_rr(XMMRegisterID mask, XMMRegisterID src1, XMMRegisterID src0, XMMRegisterID dst) { @@ -3713,20 +3142,20 @@ threeByteOpImmSimd("vblendps", VEX_PD, OP3_BLENDPS_VpsWpsIb, ESCAPE_BLENDPS, imm void vmovsldup_rr(XMMRegisterID src, XMMRegisterID dst) { - twoByteOpSimd("vmovsldup", VEX_SS, OP2_MOVSLDUP_VpsWps, src, X86Registers::invalid_xmm, dst); + twoByteOpSimd("vmovsldup", VEX_SS, OP2_MOVSLDUP_VpsWps, src, invalid_xmm, dst); } void vmovsldup_mr(int32_t offset, RegisterID base, XMMRegisterID dst) { - twoByteOpSimd("vmovsldup", VEX_SS, OP2_MOVSLDUP_VpsWps, offset, base, X86Registers::invalid_xmm, dst); + twoByteOpSimd("vmovsldup", VEX_SS, OP2_MOVSLDUP_VpsWps, offset, base, invalid_xmm, dst); } void vmovshdup_rr(XMMRegisterID src, XMMRegisterID dst) { - twoByteOpSimd("vmovshdup", VEX_SS, OP2_MOVSHDUP_VpsWps, src, X86Registers::invalid_xmm, dst); + twoByteOpSimd("vmovshdup", VEX_SS, OP2_MOVSHDUP_VpsWps, src, invalid_xmm, dst); } void vmovshdup_mr(int32_t offset, RegisterID base, XMMRegisterID dst) { - twoByteOpSimd("vmovshdup", VEX_SS, OP2_MOVSHDUP_VpsWps, offset, base, X86Registers::invalid_xmm, dst); + twoByteOpSimd("vmovshdup", VEX_SS, OP2_MOVSHDUP_VpsWps, offset, base, invalid_xmm, dst); } void vminsd_rr(XMMRegisterID src1, XMMRegisterID src0, XMMRegisterID dst) @@ -3869,17 +3298,7 @@ threeByteOpImmSimd("vblendps", VEX_PD, OP3_BLENDPS_VpsWpsIb, ESCAPE_BLENDPS, imm } // Linking & patching: - // - // 'link' and 'patch' methods are for use on unprotected code - such as the - // code within the AssemblerBuffer, and code being patched by the patch - // buffer. Once code has been finalized it is (platform support permitting) - // within a non- writable region of memory; to modify the code in an - // execute-only execuable pool the 'repatch' and 'relink' methods should be - // used. - // Like Lua's emitter, we thread jump lists through the unpatched target - // field, which will get fixed up when the label (which has a pointer to the - // head of the jump list) is bound. bool nextJump(const JmpSrc& from, JmpSrc* next) { // Sanity check - if the assembler has OOM'd, it will start overwriting @@ -3888,7 +3307,7 @@ threeByteOpImmSimd("vblendps", VEX_PD, OP3_BLENDPS_VpsWpsIb, ESCAPE_BLENDPS, imm return false; const unsigned char* code = m_formatter.data(); - int32_t offset = getInt32(code + from.offset()); + int32_t offset = GetInt32(code + from.offset()); if (offset == -1) return false; *next = JmpSrc(offset); @@ -3902,7 +3321,7 @@ threeByteOpImmSimd("vblendps", VEX_PD, OP3_BLENDPS_VpsWpsIb, ESCAPE_BLENDPS, imm return; unsigned char* code = m_formatter.data(); - setInt32(code + from.offset(), to.offset()); + SetInt32(code + from.offset(), to.offset()); } void linkJump(JmpSrc from, JmpDst to) @@ -3917,13 +3336,7 @@ threeByteOpImmSimd("vblendps", VEX_PD, OP3_BLENDPS_VpsWpsIb, ESCAPE_BLENDPS, imm spew(".set .Lfrom%d, .Llabel%d", from.offset(), to.offset()); unsigned char* code = m_formatter.data(); - setRel32(code + from.offset(), code + to.offset()); - } - - static bool canRelinkJump(void* from, void* to) - { - intptr_t offset = static_cast(to) - static_cast(from); - return (offset == static_cast(offset)); + SetRel32(code + from.offset(), code + to.offset()); } void executableCopy(void* buffer) @@ -3931,64 +3344,14 @@ threeByteOpImmSimd("vblendps", VEX_PD, OP3_BLENDPS_VpsWpsIb, ESCAPE_BLENDPS, imm memcpy(buffer, m_formatter.buffer(), size()); } - static void setRel32(void* from, void* to) - { - intptr_t offset = static_cast(to) - static_cast(from); - MOZ_ASSERT(offset == static_cast(offset), - "offset is too great for a 32-bit relocation"); - if (offset != static_cast(offset)) - MOZ_CRASH("offset is too great for a 32-bit relocation"); + private: + static bool CAN_SIGN_EXTEND_8_32(int32_t value) { return value == (int32_t)(int8_t)value; } + static bool CAN_SIGN_EXTEND_16_32(int32_t value) { return value == (int32_t)(int16_t)value; } + static bool CAN_ZERO_EXTEND_8_32(int32_t value) { return value == (int32_t)(uint8_t)value; } + static bool CAN_ZERO_EXTEND_8H_32(int32_t value) { return value == (value & 0xff00); } + static bool CAN_ZERO_EXTEND_16_32(int32_t value) { return value == (int32_t)(uint16_t)value; } + static bool CAN_ZERO_EXTEND_32_64(int32_t value) { return value >= 0; } - setInt32(from, offset); - } - - static void *getRel32Target(void* where) - { - int32_t rel = getInt32(where); - return (char *)where + rel; - } - - static void *getPointer(const void* where) - { - return static_cast(where)[-1]; - } - - static void **getPointerRef(void* where) - { - return &static_cast(where)[-1]; - } - - static void setPointer(void* where, const void* value) - { - static_cast(where)[-1] = value; - } - - // Test whether the given address will fit in an address immediate field. - // This is always true on x86, but on x64 it's only true for addreses which - // fit in the 32-bit immediate field. - static bool isAddressImmediate(const void *address) { - intptr_t value = reinterpret_cast(address); - int32_t immediate = static_cast(value); - return value == immediate; - } - - // Convert the given address to a 32-bit immediate field value. This is a - // no-op on x86, but on x64 it asserts that the address is actually a valid - // address immediate. - static int32_t addressImmediate(const void *address) { -#ifdef JS_CODEGEN_X64 - // x64's 64-bit addresses don't all fit in the 32-bit immediate. - MOZ_ASSERT(isAddressImmediate(address)); -#endif - return static_cast(reinterpret_cast(address)); - } - - static void setInt32(void* where, int32_t value) - { - static_cast(where)[-1] = value; - } - -private: // Methods for encoding SIMD instructions via either legacy SSE encoding or // VEX encoding. @@ -3996,7 +3359,7 @@ private: { // If we don't have AVX or it's disabled, use the legacy SSE encoding. if (!useVEX_) { - MOZ_ASSERT(src0 == X86Registers::invalid_xmm || src0 == dst, + MOZ_ASSERT(src0 == invalid_xmm || src0 == dst, "Legacy SSE (pre-AVX) encoding requires the output register to be " "the same as the src0 input register"); return true; @@ -4017,13 +3380,13 @@ private: MOZ_ASSERT(src0 == dst, "Legacy SSE (pre-AVX) encoding requires the output register to be " "the same as the src0 input register"); - MOZ_ASSERT(mask == X86Registers::xmm0, + MOZ_ASSERT(mask == xmm0, "Legacy SSE (pre-AVX) encoding for blendv requires the mask to be " "in xmm0"); return true; } - return src0 == dst && mask == X86Registers::xmm0; + return src0 == dst && mask == xmm0; } bool useLegacySSEEncodingForOtherOutput() @@ -4055,7 +3418,7 @@ private: m_formatter.twoByteRipOpVex(ty, opcode, 0, src0, dst); JmpSrc label(m_formatter.size()); - if (src0 == X86Registers::invalid_xmm) { + if (src0 == invalid_xmm) { if (IsXMMReversedOperands(opcode)) spew("%-11s%s, " MEM_o32r "", name, XMMRegName(dst), ADDR_o32r(label.offset())); else @@ -4080,7 +3443,7 @@ private: return; } - if (src0 == X86Registers::invalid_xmm) { + if (src0 == invalid_xmm) { if (IsXMMReversedOperands(opcode)) spew("%-11s%s, %s", name, XMMRegName(dst), XMMRegName(rm)); else @@ -4102,7 +3465,7 @@ private: return; } - if (src0 == X86Registers::invalid_xmm) + if (src0 == invalid_xmm) spew("%-11s$0x%x, %s, %s", name, imm, XMMRegName(rm), XMMRegName(dst)); else spew("%-11s$0x%x, %s, %s, %s", name, imm, XMMRegName(rm), XMMRegName(src0), XMMRegName(dst)); @@ -4126,7 +3489,7 @@ private: return; } - if (src0 == X86Registers::invalid_xmm) { + if (src0 == invalid_xmm) { if (IsXMMReversedOperands(opcode)) spew("%-11s%s, " MEM_ob, name, XMMRegName(dst), ADDR_ob(offset, base)); else @@ -4151,7 +3514,7 @@ private: return; } - if (src0 == X86Registers::invalid_xmm) { + if (src0 == invalid_xmm) { if (IsXMMReversedOperands(opcode)) spew("%-11s%s, " MEM_o32b, name, XMMRegName(dst), ADDR_o32b(offset, base)); else @@ -4198,7 +3561,7 @@ private: return; } - if (src0 == X86Registers::invalid_xmm) { + if (src0 == invalid_xmm) { if (IsXMMReversedOperands(opcode)) { spew("%-11s%s, " MEM_obs, name, XMMRegName(dst), ADDR_obs(offset, base, index, scale)); @@ -4226,7 +3589,7 @@ private: return; } - if (src0 == X86Registers::invalid_xmm) { + if (src0 == invalid_xmm) { if (IsXMMReversedOperands(opcode)) spew("%-11s%s, %p", name, XMMRegName(dst), address); else @@ -4266,7 +3629,7 @@ private: return; } - if (src0 == X86Registers::invalid_xmm) { + if (src0 == invalid_xmm) { if (IsXMMReversedOperands(opcode)) spew("%-11s%s, %s", name, XMMRegName(dst), GPReg32Name(rm)); else @@ -4291,7 +3654,7 @@ private: return; } - if (src0 == X86Registers::invalid_xmm) { + if (src0 == invalid_xmm) { if (IsXMMReversedOperands(opcode)) spew("%-11s%s, %s", name, XMMRegName(dst), GPRegName(rm)); else @@ -4324,7 +3687,7 @@ private: spew("%-11s%s, %s", name, XMMRegName((XMMRegisterID)dst), GPReg32Name((RegisterID)rm)); else spew("%-11s%s, %s", name, XMMRegName(rm), GPReg32Name(dst)); - m_formatter.twoByteOpVex(ty, opcode, (RegisterID)rm, X86Registers::invalid_xmm, dst); + m_formatter.twoByteOpVex(ty, opcode, (RegisterID)rm, invalid_xmm, dst); } void twoByteOpImmSimdInt32(const char *name, VexOperandType ty, TwoByteOpcodeID opcode, @@ -4339,7 +3702,7 @@ private: } spew("%-11s$0x%x, %s, %s", name, imm, XMMRegName(rm), GPReg32Name(dst)); - m_formatter.twoByteOpVex(ty, opcode, (RegisterID)rm, X86Registers::invalid_xmm, dst); + m_formatter.twoByteOpVex(ty, opcode, (RegisterID)rm, invalid_xmm, dst); m_formatter.immediate8u(imm); } @@ -4365,7 +3728,7 @@ private: spew("%-11s%s, %s", name, XMMRegName((XMMRegisterID)dst), GPRegName((RegisterID)rm)); else spew("%-11s%s, %s", name, XMMRegName(rm), GPRegName(dst)); - m_formatter.twoByteOpVex64(ty, opcode, (RegisterID)rm, X86Registers::invalid_xmm, (XMMRegisterID)dst); + m_formatter.twoByteOpVex64(ty, opcode, (RegisterID)rm, invalid_xmm, (XMMRegisterID)dst); } #endif @@ -4380,7 +3743,7 @@ private: } spew("%-11s%s, %s", name, XMMRegName(rm), XMMRegName(reg)); - m_formatter.twoByteOpVex(ty, opcode, (RegisterID)rm, X86Registers::invalid_xmm, (XMMRegisterID)reg); + m_formatter.twoByteOpVex(ty, opcode, (RegisterID)rm, invalid_xmm, (XMMRegisterID)reg); } void twoByteOpSimdFlags(const char *name, VexOperandType ty, TwoByteOpcodeID opcode, @@ -4396,7 +3759,7 @@ private: spew("%-11s" MEM_ob ", %s", name, ADDR_ob(offset, base), XMMRegName(reg)); - m_formatter.twoByteOpVex(ty, opcode, offset, base, X86Registers::invalid_xmm, (XMMRegisterID)reg); + m_formatter.twoByteOpVex(ty, opcode, offset, base, invalid_xmm, (XMMRegisterID)reg); } void threeByteOpSimd(const char *name, VexOperandType ty, ThreeByteOpcodeID opcode, @@ -4532,7 +3895,7 @@ private: spew("%-11s$0x%x, %s, %s", name, imm, XMMRegName((XMMRegisterID)dst), GPReg32Name((RegisterID)src)); else spew("%-11s$0x%x, %s, %s", name, imm, XMMRegName(src), GPReg32Name(dst)); - m_formatter.threeByteOpVex(ty, opcode, escape, (RegisterID)src, X86Registers::invalid_xmm, dst); + m_formatter.threeByteOpVex(ty, opcode, escape, (RegisterID)src, invalid_xmm, dst); m_formatter.immediate8u(imm); } @@ -4549,7 +3912,7 @@ private: } spew("%-11s$0x%x, " MEM_ob ", %s", name, imm, ADDR_ob(offset, base), GPReg32Name(dst)); - m_formatter.threeByteOpVex(ty, opcode, escape, offset, base, X86Registers::invalid_xmm, dst); + m_formatter.threeByteOpVex(ty, opcode, escape, offset, base, invalid_xmm, dst); m_formatter.immediate8u(imm); } @@ -4561,14 +3924,14 @@ private: spew("blendvps %s, %s", XMMRegName(rm), XMMRegName(dst)); // Even though a "ps" instruction, vblendv is encoded with the "pd" prefix. m_formatter.legacySSEPrefix(VEX_PD); - m_formatter.threeByteOp(OP3_BLENDVPS_VdqWdq, ESCAPE_BLENDVPS, (RegisterID)rm, dst); + m_formatter.threeByteOp(OP3_BLENDVPS_VdqWdq, ESCAPE_3A, (RegisterID)rm, dst); return; } spew("vblendvps %s, %s, %s, %s", XMMRegName(mask), XMMRegName(rm), XMMRegName(src0), XMMRegName(dst)); // Even though a "ps" instruction, vblendv is encoded with the "pd" prefix. - m_formatter.vblendvOpVex(VEX_PD, OP3_VBLENDVPS_VdqWdq, ESCAPE_VBLENDVPS, + m_formatter.vblendvOpVex(VEX_PD, OP3_VBLENDVPS_VdqWdq, ESCAPE_3A, mask, (RegisterID)rm, src0, dst); } @@ -4578,14 +3941,14 @@ private: spew("blendvps " MEM_ob ", %s", ADDR_ob(offset, base), XMMRegName(dst)); // Even though a "ps" instruction, vblendv is encoded with the "pd" prefix. m_formatter.legacySSEPrefix(VEX_PD); - m_formatter.threeByteOp(OP3_BLENDVPS_VdqWdq, ESCAPE_BLENDVPS, offset, base, dst); + m_formatter.threeByteOp(OP3_BLENDVPS_VdqWdq, ESCAPE_3A, offset, base, dst); return; } spew("vblendvps %s, " MEM_ob ", %s, %s", XMMRegName(mask), ADDR_ob(offset, base), XMMRegName(src0), XMMRegName(dst)); // Even though a "ps" instruction, vblendv is encoded with the "pd" prefix. - m_formatter.vblendvOpVex(VEX_PD, OP3_VBLENDVPS_VdqWdq, ESCAPE_VBLENDVPS, + m_formatter.vblendvOpVex(VEX_PD, OP3_VBLENDVPS_VdqWdq, ESCAPE_3A, mask, offset, base, src0, dst); } @@ -4605,15 +3968,8 @@ private: m_formatter.immediate8u(imm); } - static int32_t getInt32(const void* where) - { - return static_cast(where)[-1]; - } - class X86InstructionFormatter { - static const int maxInstructionSize = 16; - public: // Legacy prefix bytes: // @@ -4652,20 +4008,20 @@ private: void oneByteOp(OneByteOpcodeID opcode) { - m_buffer.ensureSpace(maxInstructionSize); + m_buffer.ensureSpace(MaxInstructionSize); m_buffer.putByteUnchecked(opcode); } void oneByteOp(OneByteOpcodeID opcode, RegisterID reg) { - m_buffer.ensureSpace(maxInstructionSize); + m_buffer.ensureSpace(MaxInstructionSize); emitRexIfNeeded(0, 0, reg); m_buffer.putByteUnchecked(opcode + (reg & 7)); } void oneByteOp(OneByteOpcodeID opcode, RegisterID rm, int reg) { - m_buffer.ensureSpace(maxInstructionSize); + m_buffer.ensureSpace(MaxInstructionSize); emitRexIfNeeded(reg, 0, rm); m_buffer.putByteUnchecked(opcode); registerModRM(rm, reg); @@ -4673,7 +4029,7 @@ private: void oneByteOp(OneByteOpcodeID opcode, int32_t offset, RegisterID base, int reg) { - m_buffer.ensureSpace(maxInstructionSize); + m_buffer.ensureSpace(MaxInstructionSize); emitRexIfNeeded(reg, 0, base); m_buffer.putByteUnchecked(opcode); memoryModRM(offset, base, reg); @@ -4681,7 +4037,7 @@ private: void oneByteOp_disp32(OneByteOpcodeID opcode, int32_t offset, RegisterID base, int reg) { - m_buffer.ensureSpace(maxInstructionSize); + m_buffer.ensureSpace(MaxInstructionSize); emitRexIfNeeded(reg, 0, base); m_buffer.putByteUnchecked(opcode); memoryModRM_disp32(offset, base, reg); @@ -4689,7 +4045,7 @@ private: void oneByteOp(OneByteOpcodeID opcode, int32_t offset, RegisterID base, RegisterID index, int scale, int reg) { - m_buffer.ensureSpace(maxInstructionSize); + m_buffer.ensureSpace(MaxInstructionSize); emitRexIfNeeded(reg, index, base); m_buffer.putByteUnchecked(opcode); memoryModRM(offset, base, index, scale, reg); @@ -4697,7 +4053,7 @@ private: void oneByteOp_disp32(OneByteOpcodeID opcode, int32_t offset, RegisterID index, int scale, int reg) { - m_buffer.ensureSpace(maxInstructionSize); + m_buffer.ensureSpace(MaxInstructionSize); emitRexIfNeeded(reg, index, 0); m_buffer.putByteUnchecked(opcode); memoryModRM_disp32(offset, index, scale, reg); @@ -4705,7 +4061,7 @@ private: void oneByteOp(OneByteOpcodeID opcode, const void* address, int reg) { - m_buffer.ensureSpace(maxInstructionSize); + m_buffer.ensureSpace(MaxInstructionSize); emitRexIfNeeded(reg, 0, 0); m_buffer.putByteUnchecked(opcode); memoryModRM_disp32(address, reg); @@ -4713,7 +4069,7 @@ private: void oneByteOp_disp32(OneByteOpcodeID opcode, const void* address, int reg) { - m_buffer.ensureSpace(maxInstructionSize); + m_buffer.ensureSpace(MaxInstructionSize); emitRexIfNeeded(reg, 0, 0); m_buffer.putByteUnchecked(opcode); memoryModRM_disp32(address, reg); @@ -4721,7 +4077,7 @@ private: #ifdef JS_CODEGEN_X64 void oneByteRipOp(OneByteOpcodeID opcode, int ripOffset, int reg) { - m_buffer.ensureSpace(maxInstructionSize); + m_buffer.ensureSpace(MaxInstructionSize); emitRexIfNeeded(reg, 0, 0); m_buffer.putByteUnchecked(opcode); putModRm(ModRmMemoryNoDisp, noBase, reg); @@ -4730,7 +4086,7 @@ private: void oneByteRipOp64(OneByteOpcodeID opcode, int ripOffset, int reg) { - m_buffer.ensureSpace(maxInstructionSize); + m_buffer.ensureSpace(MaxInstructionSize); emitRexW(reg, 0, 0); m_buffer.putByteUnchecked(opcode); putModRm(ModRmMemoryNoDisp, noBase, reg); @@ -4739,7 +4095,7 @@ private: void twoByteRipOp(TwoByteOpcodeID opcode, int ripOffset, int reg) { - m_buffer.ensureSpace(maxInstructionSize); + m_buffer.ensureSpace(MaxInstructionSize); emitRexIfNeeded(reg, 0, 0); m_buffer.putByteUnchecked(OP_2BYTE_ESCAPE); m_buffer.putByteUnchecked(opcode); @@ -4761,14 +4117,14 @@ private: void twoByteOp(TwoByteOpcodeID opcode) { - m_buffer.ensureSpace(maxInstructionSize); + m_buffer.ensureSpace(MaxInstructionSize); m_buffer.putByteUnchecked(OP_2BYTE_ESCAPE); m_buffer.putByteUnchecked(opcode); } void twoByteOp(TwoByteOpcodeID opcode, RegisterID rm, int reg) { - m_buffer.ensureSpace(maxInstructionSize); + m_buffer.ensureSpace(MaxInstructionSize); emitRexIfNeeded(reg, 0, rm); m_buffer.putByteUnchecked(OP_2BYTE_ESCAPE); m_buffer.putByteUnchecked(opcode); @@ -4787,7 +4143,7 @@ private: void twoByteOp(TwoByteOpcodeID opcode, int32_t offset, RegisterID base, int reg) { - m_buffer.ensureSpace(maxInstructionSize); + m_buffer.ensureSpace(MaxInstructionSize); emitRexIfNeeded(reg, 0, base); m_buffer.putByteUnchecked(OP_2BYTE_ESCAPE); m_buffer.putByteUnchecked(opcode); @@ -4806,7 +4162,7 @@ private: void twoByteOp_disp32(TwoByteOpcodeID opcode, int32_t offset, RegisterID base, int reg) { - m_buffer.ensureSpace(maxInstructionSize); + m_buffer.ensureSpace(MaxInstructionSize); emitRexIfNeeded(reg, 0, base); m_buffer.putByteUnchecked(OP_2BYTE_ESCAPE); m_buffer.putByteUnchecked(opcode); @@ -4825,7 +4181,7 @@ private: void twoByteOp(TwoByteOpcodeID opcode, int32_t offset, RegisterID base, RegisterID index, int scale, int reg) { - m_buffer.ensureSpace(maxInstructionSize); + m_buffer.ensureSpace(MaxInstructionSize); emitRexIfNeeded(reg, index, base); m_buffer.putByteUnchecked(OP_2BYTE_ESCAPE); m_buffer.putByteUnchecked(opcode); @@ -4845,7 +4201,7 @@ private: void twoByteOp(TwoByteOpcodeID opcode, const void* address, int reg) { - m_buffer.ensureSpace(maxInstructionSize); + m_buffer.ensureSpace(MaxInstructionSize); emitRexIfNeeded(reg, 0, 0); m_buffer.putByteUnchecked(OP_2BYTE_ESCAPE); m_buffer.putByteUnchecked(opcode); @@ -4864,7 +4220,7 @@ private: void threeByteOp(ThreeByteOpcodeID opcode, ThreeByteEscape escape, RegisterID rm, int reg) { - m_buffer.ensureSpace(maxInstructionSize); + m_buffer.ensureSpace(MaxInstructionSize); emitRexIfNeeded(reg, 0, rm); m_buffer.putByteUnchecked(OP_2BYTE_ESCAPE); m_buffer.putByteUnchecked(escape); @@ -4878,8 +4234,8 @@ private: int r = (reg >> 3), x = 0, b = (rm >> 3); int m = 0, w = 0, v = src0, l = 0; switch (escape) { - case 0x38: m = 2; break; // 0x0F 0x38 - case 0x3A: m = 3; break; // 0x0F 0x3A + case ESCAPE_38: m = 2; break; + case ESCAPE_3A: m = 3; break; default: MOZ_CRASH("unexpected escape"); } threeOpVex(ty, r, x, b, m, w, v, l, opcode); @@ -4888,7 +4244,7 @@ private: void threeByteOp(ThreeByteOpcodeID opcode, ThreeByteEscape escape, int32_t offset, RegisterID base, int reg) { - m_buffer.ensureSpace(maxInstructionSize); + m_buffer.ensureSpace(MaxInstructionSize); emitRexIfNeeded(reg, 0, base); m_buffer.putByteUnchecked(OP_2BYTE_ESCAPE); m_buffer.putByteUnchecked(escape); @@ -4902,8 +4258,8 @@ private: int r = (reg >> 3), x = 0, b = (base >> 3); int m = 0, w = 0, v = src0, l = 0; switch (escape) { - case 0x38: m = 2; break; // 0x0F 0x38 - case 0x3A: m = 3; break; // 0x0F 0x3A + case ESCAPE_38: m = 2; break; + case ESCAPE_3A: m = 3; break; default: MOZ_CRASH("unexpected escape"); } threeOpVex(ty, r, x, b, m, w, v, l, opcode); @@ -4912,7 +4268,7 @@ private: void threeByteOp(ThreeByteOpcodeID opcode, ThreeByteEscape escape, const void* address, int reg) { - m_buffer.ensureSpace(maxInstructionSize); + m_buffer.ensureSpace(MaxInstructionSize); emitRexIfNeeded(reg, 0, 0); m_buffer.putByteUnchecked(OP_2BYTE_ESCAPE); m_buffer.putByteUnchecked(escape); @@ -4926,8 +4282,8 @@ private: int r = (reg >> 3), x = 0, b = 0; int m = 0, w = 0, v = src0, l = 0; switch (escape) { - case 0x38: m = 2; break; // 0x0F 0x38 - case 0x3A: m = 3; break; // 0x0F 0x3A + case ESCAPE_38: m = 2; break; + case ESCAPE_3A: m = 3; break; default: MOZ_CRASH("unexpected escape"); } threeOpVex(ty, r, x, b, m, w, v, l, opcode); @@ -4940,8 +4296,8 @@ private: int r = (reg >> 3), x = 0, b = (rm >> 3); int m = 0, w = 0, v = src0, l = 0; switch (escape) { - case 0x38: m = 2; break; // 0x0F 0x38 - case 0x3A: m = 3; break; // 0x0F 0x3A + case ESCAPE_38: m = 2; break; + case ESCAPE_3A: m = 3; break; default: MOZ_CRASH("unexpected escape"); } threeOpVex(ty, r, x, b, m, w, v, l, opcode); @@ -4955,8 +4311,8 @@ private: int r = (reg >> 3), x = 0, b = (base >> 3); int m = 0, w = 0, v = src0, l = 0; switch (escape) { - case 0x38: m = 2; break; // 0x0F 0x38 - case 0x3A: m = 3; break; // 0x0F 0x3A + case ESCAPE_38: m = 2; break; + case ESCAPE_3A: m = 3; break; default: MOZ_CRASH("unexpected escape"); } threeOpVex(ty, r, x, b, m, w, v, l, opcode); @@ -4973,21 +4329,21 @@ private: void oneByteOp64(OneByteOpcodeID opcode) { - m_buffer.ensureSpace(maxInstructionSize); + m_buffer.ensureSpace(MaxInstructionSize); emitRexW(0, 0, 0); m_buffer.putByteUnchecked(opcode); } void oneByteOp64(OneByteOpcodeID opcode, RegisterID reg) { - m_buffer.ensureSpace(maxInstructionSize); + m_buffer.ensureSpace(MaxInstructionSize); emitRexW(0, 0, reg); m_buffer.putByteUnchecked(opcode + (reg & 7)); } void oneByteOp64(OneByteOpcodeID opcode, RegisterID rm, int reg) { - m_buffer.ensureSpace(maxInstructionSize); + m_buffer.ensureSpace(MaxInstructionSize); emitRexW(reg, 0, rm); m_buffer.putByteUnchecked(opcode); registerModRM(rm, reg); @@ -4995,7 +4351,7 @@ private: void oneByteOp64(OneByteOpcodeID opcode, int32_t offset, RegisterID base, int reg) { - m_buffer.ensureSpace(maxInstructionSize); + m_buffer.ensureSpace(MaxInstructionSize); emitRexW(reg, 0, base); m_buffer.putByteUnchecked(opcode); memoryModRM(offset, base, reg); @@ -5003,7 +4359,7 @@ private: void oneByteOp64_disp32(OneByteOpcodeID opcode, int32_t offset, RegisterID base, int reg) { - m_buffer.ensureSpace(maxInstructionSize); + m_buffer.ensureSpace(MaxInstructionSize); emitRexW(reg, 0, base); m_buffer.putByteUnchecked(opcode); memoryModRM_disp32(offset, base, reg); @@ -5011,7 +4367,7 @@ private: void oneByteOp64(OneByteOpcodeID opcode, int32_t offset, RegisterID base, RegisterID index, int scale, int reg) { - m_buffer.ensureSpace(maxInstructionSize); + m_buffer.ensureSpace(MaxInstructionSize); emitRexW(reg, index, base); m_buffer.putByteUnchecked(opcode); memoryModRM(offset, base, index, scale, reg); @@ -5019,7 +4375,7 @@ private: void oneByteOp64(OneByteOpcodeID opcode, const void* address, int reg) { - m_buffer.ensureSpace(maxInstructionSize); + m_buffer.ensureSpace(MaxInstructionSize); emitRexW(reg, 0, 0); m_buffer.putByteUnchecked(opcode); memoryModRM(address, reg); @@ -5027,7 +4383,7 @@ private: void twoByteOp64(TwoByteOpcodeID opcode, RegisterID rm, int reg) { - m_buffer.ensureSpace(maxInstructionSize); + m_buffer.ensureSpace(MaxInstructionSize); emitRexW(reg, 0, rm); m_buffer.putByteUnchecked(OP_2BYTE_ESCAPE); m_buffer.putByteUnchecked(opcode); @@ -5058,36 +4414,36 @@ private: // byte of the second four registers (spl..dil). // // Address operands should still be checked using regRequiresRex(), - // while ByteRegRequiresRex() is provided to check byte register + // while byteRegRequiresRex() is provided to check byte register // operands. void oneByteOp8(OneByteOpcodeID opcode) { - m_buffer.ensureSpace(maxInstructionSize); + m_buffer.ensureSpace(MaxInstructionSize); m_buffer.putByteUnchecked(opcode); } void oneByteOp8(OneByteOpcodeID opcode, RegisterID rm, GroupOpcodeID groupOp) { - m_buffer.ensureSpace(maxInstructionSize); - emitRexIf(X86Registers::ByteRegRequiresRex(rm), 0, 0, rm); + m_buffer.ensureSpace(MaxInstructionSize); + emitRexIf(byteRegRequiresRex(rm), 0, 0, rm); m_buffer.putByteUnchecked(opcode); registerModRM(rm, groupOp); } // Like oneByteOp8, but never emits a REX prefix. - void oneByteOp8_norex(OneByteOpcodeID opcode, RegisterID rm, GroupOpcodeID groupOp) + void oneByteOp8_norex(OneByteOpcodeID opcode, HRegisterID rm, GroupOpcodeID groupOp) { - MOZ_ASSERT(!regRequiresRex(rm)); - m_buffer.ensureSpace(maxInstructionSize); + MOZ_ASSERT(!regRequiresRex(RegisterID(rm))); + m_buffer.ensureSpace(MaxInstructionSize); m_buffer.putByteUnchecked(opcode); - registerModRM(rm, groupOp); + registerModRM(RegisterID(rm), groupOp); } void oneByteOp8(OneByteOpcodeID opcode, int32_t offset, RegisterID base, RegisterID reg) { - m_buffer.ensureSpace(maxInstructionSize); - emitRexIf(X86Registers::ByteRegRequiresRex(reg), reg, 0, base); + m_buffer.ensureSpace(MaxInstructionSize); + emitRexIf(byteRegRequiresRex(reg), reg, 0, base); m_buffer.putByteUnchecked(opcode); memoryModRM(offset, base, reg); } @@ -5095,8 +4451,8 @@ private: void oneByteOp8_disp32(OneByteOpcodeID opcode, int32_t offset, RegisterID base, RegisterID reg) { - m_buffer.ensureSpace(maxInstructionSize); - emitRexIf(X86Registers::ByteRegRequiresRex(reg), reg, 0, base); + m_buffer.ensureSpace(MaxInstructionSize); + emitRexIf(byteRegRequiresRex(reg), reg, 0, base); m_buffer.putByteUnchecked(opcode); memoryModRM_disp32(offset, base, reg); } @@ -5104,24 +4460,24 @@ private: void oneByteOp8(OneByteOpcodeID opcode, int32_t offset, RegisterID base, RegisterID index, int scale, RegisterID reg) { - m_buffer.ensureSpace(maxInstructionSize); - emitRexIf(X86Registers::ByteRegRequiresRex(reg), reg, index, base); + m_buffer.ensureSpace(MaxInstructionSize); + emitRexIf(byteRegRequiresRex(reg), reg, index, base); m_buffer.putByteUnchecked(opcode); memoryModRM(offset, base, index, scale, reg); } void oneByteOp8(OneByteOpcodeID opcode, const void* address, RegisterID reg) { - m_buffer.ensureSpace(maxInstructionSize); - emitRexIf(X86Registers::ByteRegRequiresRex(reg), reg, 0, 0); + m_buffer.ensureSpace(MaxInstructionSize); + emitRexIf(byteRegRequiresRex(reg), reg, 0, 0); m_buffer.putByteUnchecked(opcode); memoryModRM_disp32(address, reg); } void twoByteOp8(TwoByteOpcodeID opcode, RegisterID rm, RegisterID reg) { - m_buffer.ensureSpace(maxInstructionSize); - emitRexIf(X86Registers::ByteRegRequiresRex(reg)|X86Registers::ByteRegRequiresRex(rm), reg, 0, rm); + m_buffer.ensureSpace(MaxInstructionSize); + emitRexIf(byteRegRequiresRex(reg)|byteRegRequiresRex(rm), reg, 0, rm); m_buffer.putByteUnchecked(OP_2BYTE_ESCAPE); m_buffer.putByteUnchecked(opcode); registerModRM(rm, reg); @@ -5133,8 +4489,8 @@ private: // prefix to disambiguate it from ah..bh. void twoByteOp8_movx(TwoByteOpcodeID opcode, RegisterID rm, RegisterID reg) { - m_buffer.ensureSpace(maxInstructionSize); - emitRexIf(regRequiresRex(reg)|X86Registers::ByteRegRequiresRex(rm), reg, 0, rm); + m_buffer.ensureSpace(MaxInstructionSize); + emitRexIf(regRequiresRex(reg)|byteRegRequiresRex(rm), reg, 0, rm); m_buffer.putByteUnchecked(OP_2BYTE_ESCAPE); m_buffer.putByteUnchecked(opcode); registerModRM(rm, reg); @@ -5142,8 +4498,8 @@ private: void twoByteOp8(TwoByteOpcodeID opcode, RegisterID rm, GroupOpcodeID groupOp) { - m_buffer.ensureSpace(maxInstructionSize); - emitRexIf(X86Registers::ByteRegRequiresRex(rm), 0, 0, rm); + m_buffer.ensureSpace(MaxInstructionSize); + emitRexIf(byteRegRequiresRex(rm), 0, 0, rm); m_buffer.putByteUnchecked(OP_2BYTE_ESCAPE); m_buffer.putByteUnchecked(opcode); registerModRM(rm, groupOp); @@ -5274,19 +4630,28 @@ private: // Internals; ModRm and REX formatters. - static const RegisterID noBase = X86Registers::ebp; - static const RegisterID hasSib = X86Registers::esp; - static const RegisterID noIndex = X86Registers::esp; -#ifdef JS_CODEGEN_X64 - static const RegisterID noBase2 = X86Registers::r13; - static const RegisterID hasSib2 = X86Registers::r12; - - // Registers r8 & above require a REX prefixe. - bool regRequiresRex(int reg) + // Byte operand register spl & above requir a REX prefix, which precludes + // use of the h registers in the same instruction. + static bool byteRegRequiresRex(RegisterID reg) { - return (reg >= X86Registers::r8); +#ifdef JS_CODEGEN_X64 + return reg >= rsp; +#else + return false; +#endif } + // For non-byte sizes, registers r8 & above always require a REX prefix. + static bool regRequiresRex(RegisterID reg) + { +#ifdef JS_CODEGEN_X64 + return reg >= r8; +#else + return false; +#endif + } + +#ifdef JS_CODEGEN_X64 // Format a REX prefix byte. void emitRex(bool w, int r, int x, int b) { @@ -5299,7 +4664,7 @@ private: emitRex(true, r, x, b); } - // Used for operations with byte operands - use ByteRegRequiresRex() to + // Used for operations with byte operands - use byteRegRequiresRex() to // check register operands, regRequiresRex() to check other registers // (i.e. address base & index). // @@ -5308,19 +4673,23 @@ private: // oneByteOp8 functionality such that r, x, and b can all be used. void emitRexIf(bool condition, int r, int x, int b) { - if (condition || regRequiresRex(r) || regRequiresRex(x) || regRequiresRex(b)) + if (condition || + regRequiresRex(RegisterID(r)) || + regRequiresRex(RegisterID(x)) || + regRequiresRex(RegisterID(b))) + { emitRex(false, r, x, b); + } } // Used for word sized operations, will plant a REX prefix if necessary // (if any register is r8 or above). void emitRexIfNeeded(int r, int x, int b) { - emitRexIf(regRequiresRex(r) || regRequiresRex(x) || regRequiresRex(b), r, x, b); + emitRexIf(false, r, x, b); } #else // No REX prefix bytes on 32-bit x86. - bool regRequiresRex(int) { return false; } void emitRexIf(bool condition, int, int, int) { MOZ_ASSERT(!condition, "32-bit x86 should never use a REX prefix"); @@ -5328,13 +4697,6 @@ private: void emitRexIfNeeded(int, int, int) {} #endif - enum ModRmMode { - ModRmMemoryNoDisp, - ModRmMemoryDisp8, - ModRmMemoryDisp32, - ModRmRegister - }; - void putModRm(ModRmMode mode, RegisterID rm, int reg) { m_buffer.putByteUnchecked((mode << 6) | ((reg & 7) << 3) | (rm & 7)); @@ -5450,7 +4812,7 @@ private: void memoryModRM_disp32(const void* address, int reg) { - int32_t disp = addressImmediate(address); + int32_t disp = AddressImmediate(address); #ifdef JS_CODEGEN_X64 // On x64-64, non-RIP-relative absolute mode requires a SIB. @@ -5470,9 +4832,9 @@ private: void threeOpVex(VexOperandType p, int r, int x, int b, int m, int w, int v, int l, int opcode) { - m_buffer.ensureSpace(maxInstructionSize); + m_buffer.ensureSpace(MaxInstructionSize); - if (v == X86Registers::invalid_xmm) + if (v == invalid_xmm) v = XMMRegisterID(0); if (x == 0 && b == 0 && m == 1 && w == 0) { @@ -5495,6 +4857,8 @@ private: bool useVEX_; }; +} // namespace X86Encoding + } // namespace jit } // namespace js diff --git a/js/src/jit/shared/CodeGenerator-x86-shared.cpp b/js/src/jit/shared/CodeGenerator-x86-shared.cpp index 461df0f66c2..61486cb4af4 100644 --- a/js/src/jit/shared/CodeGenerator-x86-shared.cpp +++ b/js/src/jit/shared/CodeGenerator-x86-shared.cpp @@ -1601,7 +1601,7 @@ CodeGeneratorX86Shared::visitFloor(LFloor *lir) bailoutFrom(&bailout, lir->snapshot()); // Round toward -Infinity. - masm.vroundsd(X86Assembler::RoundDown, input, scratch, scratch); + masm.vroundsd(X86Encoding::RoundDown, input, scratch, scratch); bailoutCvttsd2si(scratch, output, lir->snapshot()); } else { @@ -1658,7 +1658,7 @@ CodeGeneratorX86Shared::visitFloorF(LFloorF *lir) bailoutFrom(&bailout, lir->snapshot()); // Round toward -Infinity. - masm.vroundss(X86Assembler::RoundDown, input, scratch, scratch); + masm.vroundss(X86Encoding::RoundDown, input, scratch, scratch); bailoutCvttss2si(scratch, output, lir->snapshot()); } else { @@ -1723,7 +1723,7 @@ CodeGeneratorX86Shared::visitCeil(LCeil *lir) // x <= -1 or x > -0 masm.bind(&lessThanMinusOne); // Round toward +Infinity. - masm.vroundsd(X86Assembler::RoundUp, input, scratch, scratch); + masm.vroundsd(X86Encoding::RoundUp, input, scratch, scratch); bailoutCvttsd2si(scratch, output, lir->snapshot()); return; } @@ -1775,7 +1775,7 @@ CodeGeneratorX86Shared::visitCeilF(LCeilF *lir) // x <= -1 or x > -0 masm.bind(&lessThanMinusOne); // Round toward +Infinity. - masm.vroundss(X86Assembler::RoundUp, input, scratch, scratch); + masm.vroundss(X86Encoding::RoundUp, input, scratch, scratch); bailoutCvttss2si(scratch, output, lir->snapshot()); return; } @@ -1850,7 +1850,7 @@ CodeGeneratorX86Shared::visitRound(LRound *lir) // Add 0.5 and round toward -Infinity. The result is stored in the temp // register (currently contains 0.5). masm.addDouble(input, temp); - masm.vroundsd(X86Assembler::RoundDown, temp, scratch, scratch); + masm.vroundsd(X86Encoding::RoundDown, temp, scratch, scratch); // Truncate. bailoutCvttsd2si(scratch, output, lir->snapshot()); @@ -1933,7 +1933,7 @@ CodeGeneratorX86Shared::visitRoundF(LRoundF *lir) // Add 0.5 and round toward -Infinity. The result is stored in the temp // register (currently contains 0.5). masm.addFloat32(input, temp); - masm.vroundss(X86Assembler::RoundDown, temp, scratch, scratch); + masm.vroundss(X86Encoding::RoundDown, temp, scratch, scratch); // Truncate. bailoutCvttss2si(scratch, output, lir->snapshot()); diff --git a/js/src/jit/shared/Constants-x86-shared.h b/js/src/jit/shared/Constants-x86-shared.h new file mode 100644 index 00000000000..aff6a1d8c68 --- /dev/null +++ b/js/src/jit/shared/Constants-x86-shared.h @@ -0,0 +1,222 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef jit_shared_Constants_x86_shared_h +#define jit_shared_Constants_x86_shared_h + +namespace js { +namespace jit { + +namespace X86Encoding { + +enum RegisterID { + rax, rcx, rdx, rbx, rsp, rbp, rsi, rdi +#ifdef JS_CODEGEN_X64 + ,r8, r9, r10, r11, r12, r13, r14, r15 +#endif + ,invalid_reg +}; + +enum HRegisterID { + ah = rsp, + ch = rbp, + dh = rsi, + bh = rdi +}; + +enum XMMRegisterID { + xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7 +#ifdef JS_CODEGEN_X64 + ,xmm8, xmm9, xmm10, xmm11, xmm12, xmm13, xmm14, xmm15 +#endif + ,invalid_xmm +}; + +inline const char *XMMRegName(XMMRegisterID reg) +{ + static const char *const names[] = { + "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7" +#ifdef JS_CODEGEN_X64 + ,"%xmm8", "%xmm9", "%xmm10", "%xmm11", "%xmm12", "%xmm13", "%xmm14", "%xmm15" +#endif + }; + MOZ_ASSERT(size_t(reg) < mozilla::ArrayLength(names)); + return names[reg]; +} + +#ifdef JS_CODEGEN_X64 +inline const char *GPReg64Name(RegisterID reg) +{ + static const char *const names[] = { + "%rax", "%rcx", "%rdx", "%rbx", "%rsp", "%rbp", "%rsi", "%rdi" +#ifdef JS_CODEGEN_X64 + ,"%r8", "%r9", "%r10", "%r11", "%r12", "%r13", "%r14", "%r15" +#endif + }; + MOZ_ASSERT(size_t(reg) < mozilla::ArrayLength(names)); + return names[reg]; +} +#endif + +inline const char *GPReg32Name(RegisterID reg) +{ + static const char *const names[] = { + "%eax", "%ecx", "%edx", "%ebx", "%esp", "%ebp", "%esi", "%edi" +#ifdef JS_CODEGEN_X64 + ,"%r8d", "%r9d", "%r10d", "%r11d", "%r12d", "%r13d", "%r14d", "%r15d" +#endif + }; + MOZ_ASSERT(size_t(reg) < mozilla::ArrayLength(names)); + return names[reg]; +} + +inline const char *GPReg16Name(RegisterID reg) +{ + static const char *const names[] = { + "%ax", "%cx", "%dx", "%bx", "%sp", "%bp", "%si", "%di" +#ifdef JS_CODEGEN_X64 + ,"%r8w", "%r9w", "%r10w", "%r11w", "%r12w", "%r13w", "%r14w", "%r15w" +#endif + }; + MOZ_ASSERT(size_t(reg) < mozilla::ArrayLength(names)); + return names[reg]; +} + +inline const char *GPReg8Name(RegisterID reg) +{ + static const char *const names[] = { + "%al", "%cl", "%dl", "%bl" +#ifdef JS_CODEGEN_X64 + ,"%spl", "%bpl", "%sil", "%dil", + "%r8b", "%r9b", "%r10b", "%r11b", "%r12b", "%r13b", "%r14b", "%r15b" +#endif + }; + MOZ_ASSERT(size_t(reg) < mozilla::ArrayLength(names)); + return names[reg]; +} + +inline const char *GPRegName(RegisterID reg) +{ +#ifdef JS_CODEGEN_X64 + return GPReg64Name(reg); +#else + return GPReg32Name(reg); +#endif +} + +inline bool HasSubregL(RegisterID reg) +{ +#ifdef JS_CODEGEN_X64 + // In 64-bit mode, all registers have an 8-bit lo subreg. + return true; +#else + // In 32-bit mode, only the first four registers do. + return reg <= rbx; +#endif +} + +inline bool HasSubregH(RegisterID reg) +{ + // The first four registers always have h registers. However, note that + // on x64, h registers may not be used in instructions using REX + // prefixes. Also note that this may depend on what other registers are + // used! + return reg <= rbx; +} + +inline HRegisterID GetSubregH(RegisterID reg) +{ + MOZ_ASSERT(HasSubregH(reg)); + return HRegisterID(reg + 4); +} + +inline const char *HRegName8(HRegisterID reg) +{ + static const char *const names[] = { + "%ah", "%ch", "%dh", "%bh" + }; + size_t index = reg - GetSubregH(rax); + MOZ_ASSERT(index < mozilla::ArrayLength(names)); + return names[index]; +} + +enum Condition { + ConditionO, + ConditionNO, + ConditionB, + ConditionAE, + ConditionE, + ConditionNE, + ConditionBE, + ConditionA, + ConditionS, + ConditionNS, + ConditionP, + ConditionNP, + ConditionL, + ConditionGE, + ConditionLE, + ConditionG, + + ConditionC = ConditionB, + ConditionNC = ConditionAE +}; + +inline const char *CCName(Condition cc) +{ + static const char *const names[] = { + "o ", "no", "b ", "ae", "e ", "ne", "be", "a ", + "s ", "ns", "p ", "np", "l ", "ge", "le", "g " + }; + MOZ_ASSERT(size_t(cc) < mozilla::ArrayLength(names)); + return names[cc]; +} + +// Conditions for CMP instructions (CMPSS, CMPSD, CMPPS, CMPPD, etc). +enum ConditionCmp { + ConditionCmp_EQ = 0x0, + ConditionCmp_LT = 0x1, + ConditionCmp_LE = 0x2, + ConditionCmp_UNORD = 0x3, + ConditionCmp_NEQ = 0x4, + ConditionCmp_NLT = 0x5, + ConditionCmp_NLE = 0x6, + ConditionCmp_ORD = 0x7, +}; + +// Rounding modes for ROUNDSD. +enum RoundingMode { + RoundToNearest = 0x0, + RoundDown = 0x1, + RoundUp = 0x2, + RoundToZero = 0x3 +}; + +// Test whether the given address will fit in an address immediate field. +// This is always true on x86, but on x64 it's only true for addreses which +// fit in the 32-bit immediate field. +inline bool IsAddressImmediate(const void *address) +{ + intptr_t value = reinterpret_cast(address); + int32_t immediate = static_cast(value); + return value == immediate; +} + +// Convert the given address to a 32-bit immediate field value. This is a +// no-op on x86, but on x64 it asserts that the address is actually a valid +// address immediate. +inline int32_t AddressImmediate(const void *address) +{ + MOZ_ASSERT(IsAddressImmediate(address)); + return static_cast(reinterpret_cast(address)); +} + +} // namespace X86Encoding + +} // namespace jit +} // namespace js + +#endif /* jit_shared_Constants_x86_shared_h */ diff --git a/js/src/jit/shared/Encoding-x86-shared.h b/js/src/jit/shared/Encoding-x86-shared.h new file mode 100644 index 00000000000..a513fbd4e0f --- /dev/null +++ b/js/src/jit/shared/Encoding-x86-shared.h @@ -0,0 +1,317 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef jit_shared_Encoding_x86_shared_h +#define jit_shared_Encoding_x86_shared_h + +#include "jit/shared/Constants-x86-shared.h" + +namespace js { +namespace jit { + +namespace X86Encoding { + +static const size_t MaxInstructionSize = 16; + +enum OneByteOpcodeID { + OP_ADD_EbGb = 0x00, + OP_ADD_EvGv = 0x01, + OP_ADD_GvEv = 0x03, + OP_ADD_EAXIv = 0x05, + OP_OR_EbGb = 0x08, + OP_OR_EvGv = 0x09, + OP_OR_GvEv = 0x0B, + OP_OR_EAXIv = 0x0D, + OP_2BYTE_ESCAPE = 0x0F, + OP_AND_EbGb = 0x20, + OP_AND_EvGv = 0x21, + OP_AND_GvEv = 0x23, + OP_AND_EAXIv = 0x25, + OP_SUB_EbGb = 0x28, + OP_SUB_EvGv = 0x29, + OP_SUB_GvEv = 0x2B, + OP_SUB_EAXIv = 0x2D, + PRE_PREDICT_BRANCH_NOT_TAKEN = 0x2E, + OP_XOR_EbGb = 0x30, + OP_XOR_EvGv = 0x31, + OP_XOR_GvEv = 0x33, + OP_XOR_EAXIv = 0x35, + OP_CMP_EvGv = 0x39, + OP_CMP_GvEv = 0x3B, + OP_CMP_EAXIv = 0x3D, +#ifdef JS_CODEGEN_X64 + PRE_REX = 0x40, +#endif + OP_PUSH_EAX = 0x50, + OP_POP_EAX = 0x58, +#ifdef JS_CODEGEN_X86 + OP_PUSHA = 0x60, + OP_POPA = 0x61, +#endif +#ifdef JS_CODEGEN_X64 + OP_MOVSXD_GvEv = 0x63, +#endif + PRE_OPERAND_SIZE = 0x66, + PRE_SSE_66 = 0x66, + OP_PUSH_Iz = 0x68, + OP_IMUL_GvEvIz = 0x69, + OP_PUSH_Ib = 0x6a, + OP_IMUL_GvEvIb = 0x6b, + OP_JCC_rel8 = 0x70, + OP_GROUP1_EbIb = 0x80, + OP_GROUP1_EvIz = 0x81, + OP_GROUP1_EvIb = 0x83, + OP_TEST_EbGb = 0x84, + OP_TEST_EvGv = 0x85, + OP_XCHG_GvEv = 0x87, + OP_MOV_EbGv = 0x88, + OP_MOV_EvGv = 0x89, + OP_MOV_GvEb = 0x8A, + OP_MOV_GvEv = 0x8B, + OP_LEA = 0x8D, + OP_GROUP1A_Ev = 0x8F, + OP_NOP = 0x90, + OP_PUSHFLAGS = 0x9C, + OP_POPFLAGS = 0x9D, + OP_CDQ = 0x99, + OP_MOV_EAXOv = 0xA1, + OP_MOV_OvEAX = 0xA3, + OP_TEST_EAXIb = 0xA8, + OP_TEST_EAXIv = 0xA9, + OP_MOV_EAXIv = 0xB8, + OP_GROUP2_EvIb = 0xC1, + OP_RET_Iz = 0xC2, + PRE_VEX_C4 = 0xC4, + PRE_VEX_C5 = 0xC5, + OP_RET = 0xC3, + OP_GROUP11_EvIb = 0xC6, + OP_GROUP11_EvIz = 0xC7, + OP_INT3 = 0xCC, + OP_GROUP2_Ev1 = 0xD1, + OP_GROUP2_EvCL = 0xD3, + OP_FPU6 = 0xDD, + OP_FPU6_F32 = 0xD9, + OP_CALL_rel32 = 0xE8, + OP_JMP_rel32 = 0xE9, + OP_JMP_rel8 = 0xEB, + PRE_LOCK = 0xF0, + PRE_SSE_F2 = 0xF2, + PRE_SSE_F3 = 0xF3, + OP_HLT = 0xF4, + OP_GROUP3_EbIb = 0xF6, + OP_GROUP3_Ev = 0xF7, + OP_GROUP3_EvIz = 0xF7, // OP_GROUP3_Ev has an immediate, when instruction is a test. + OP_GROUP5_Ev = 0xFF +}; + +enum class ShiftID { + vpsrld = 2, + vpsrlq = 2, + vpsrldq = 3, + vpsrad = 4, + vpslld = 6, + vpsllq = 6 +}; + +enum TwoByteOpcodeID { + OP2_UD2 = 0x0B, + OP2_MOVSD_VsdWsd = 0x10, + OP2_MOVPS_VpsWps = 0x10, + OP2_MOVSD_WsdVsd = 0x11, + OP2_MOVPS_WpsVps = 0x11, + OP2_MOVHLPS_VqUq = 0x12, + OP2_MOVSLDUP_VpsWps = 0x12, + OP2_UNPCKLPS_VsdWsd = 0x14, + OP2_UNPCKHPS_VsdWsd = 0x15, + OP2_MOVLHPS_VqUq = 0x16, + OP2_MOVSHDUP_VpsWps = 0x16, + OP2_MOVAPD_VsdWsd = 0x28, + OP2_MOVAPS_VsdWsd = 0x28, + OP2_MOVAPS_WsdVsd = 0x29, + OP2_CVTSI2SD_VsdEd = 0x2A, + OP2_CVTTSD2SI_GdWsd = 0x2C, + OP2_UCOMISD_VsdWsd = 0x2E, + OP2_MOVMSKPD_EdVd = 0x50, + OP2_ANDPS_VpsWps = 0x54, + OP2_ANDNPS_VpsWps = 0x55, + OP2_ORPS_VpsWps = 0x56, + OP2_XORPS_VpsWps = 0x57, + OP2_ADDSD_VsdWsd = 0x58, + OP2_ADDPS_VpsWps = 0x58, + OP2_MULSD_VsdWsd = 0x59, + OP2_MULPS_VpsWps = 0x59, + OP2_CVTSS2SD_VsdEd = 0x5A, + OP2_CVTSD2SS_VsdEd = 0x5A, + OP2_CVTTPS2DQ_VdqWps = 0x5B, + OP2_CVTDQ2PS_VpsWdq = 0x5B, + OP2_SUBSD_VsdWsd = 0x5C, + OP2_SUBPS_VpsWps = 0x5C, + OP2_MINSD_VsdWsd = 0x5D, + OP2_MINSS_VssWss = 0x5D, + OP2_MINPS_VpsWps = 0x5D, + OP2_DIVSD_VsdWsd = 0x5E, + OP2_DIVPS_VpsWps = 0x5E, + OP2_MAXSD_VsdWsd = 0x5F, + OP2_MAXSS_VssWss = 0x5F, + OP2_MAXPS_VpsWps = 0x5F, + OP2_SQRTSD_VsdWsd = 0x51, + OP2_SQRTSS_VssWss = 0x51, + OP2_SQRTPS_VpsWps = 0x51, + OP2_RSQRTPS_VpsWps = 0x52, + OP2_RCPPS_VpsWps = 0x53, + OP2_ANDPD_VpdWpd = 0x54, + OP2_ORPD_VpdWpd = 0x56, + OP2_XORPD_VpdWpd = 0x57, + OP2_PCMPGTD_VdqWdq = 0x66, + OP2_MOVD_VdEd = 0x6E, + OP2_MOVDQ_VsdWsd = 0x6F, + OP2_MOVDQ_VdqWdq = 0x6F, + OP2_PSHUFD_VdqWdqIb = 0x70, + OP2_PSLLD_UdqIb = 0x72, + OP2_PSRAD_UdqIb = 0x72, + OP2_PSRLD_UdqIb = 0x72, + OP2_PSRLDQ_Vd = 0x73, + OP2_PCMPEQW = 0x75, + OP2_PCMPEQD_VdqWdq = 0x76, + OP2_MOVD_EdVd = 0x7E, + OP2_MOVDQ_WdqVdq = 0x7F, + OP2_JCC_rel32 = 0x80, + OP_SETCC = 0x90, + OP_FENCE = 0xAE, + OP2_IMUL_GvEv = 0xAF, + OP2_CMPXCHG_GvEb = 0xB0, + OP2_CMPXCHG_GvEw = 0xB1, + OP2_BSR_GvEv = 0xBD, + OP2_MOVSX_GvEb = 0xBE, + OP2_MOVSX_GvEw = 0xBF, + OP2_MOVZX_GvEb = 0xB6, + OP2_MOVZX_GvEw = 0xB7, + OP2_XADD_EbGb = 0xC0, + OP2_XADD_EvGv = 0xC1, + OP2_CMPPS_VpsWps = 0xC2, + OP2_PEXTRW_GdUdIb = 0xC5, + OP2_SHUFPS_VpsWpsIb = 0xC6, + OP2_PSRLD_VdqWdq = 0xD2, + OP2_PANDDQ_VdqWdq = 0xDB, + OP2_PANDNDQ_VdqWdq = 0xDF, + OP2_PSRAD_VdqWdq = 0xE2, + OP2_PORDQ_VdqWdq = 0xEB, + OP2_PXORDQ_VdqWdq = 0xEF, + OP2_PSLLD_VdqWdq = 0xF2, + OP2_PMULUDQ_VdqWdq = 0xF4, + OP2_PSUBD_VdqWdq = 0xFA, + OP2_PADDD_VdqWdq = 0xFE +}; + +enum ThreeByteOpcodeID { + OP3_ROUNDSS_VsdWsd = 0x0A, + OP3_ROUNDSD_VsdWsd = 0x0B, + OP3_BLENDVPS_VdqWdq = 0x14, + OP3_PEXTRD_EdVdqIb = 0x16, + OP3_BLENDPS_VpsWpsIb = 0x0C, + OP3_PTEST_VdVd = 0x17, + OP3_INSERTPS_VpsUps = 0x21, + OP3_PINSRD_VdqEdIb = 0x22, + OP3_PMULLD_VdqWdq = 0x40, + OP3_VBLENDVPS_VdqWdq = 0x4A +}; + +// Test whether the given opcode should be printed with its operands reversed. +inline bool IsXMMReversedOperands(TwoByteOpcodeID opcode) +{ + switch (opcode) { + case OP2_MOVSD_WsdVsd: // also OP2_MOVPS_WpsVps + case OP2_MOVAPS_WsdVsd: + case OP2_MOVDQ_WdqVdq: + case OP3_PEXTRD_EdVdqIb: + return true; + default: + break; + } + return false; +} + +enum ThreeByteEscape { + ESCAPE_38 = 0x38, + ESCAPE_3A = 0x3A +}; + +enum VexOperandType { + VEX_PS = 0, + VEX_PD = 1, + VEX_SS = 2, + VEX_SD = 3 +}; + +inline OneByteOpcodeID jccRel8(Condition cond) +{ + return OneByteOpcodeID(OP_JCC_rel8 + cond); +} +inline TwoByteOpcodeID jccRel32(Condition cond) +{ + return TwoByteOpcodeID(OP2_JCC_rel32 + cond); +} +inline TwoByteOpcodeID setccOpcode(Condition cond) +{ + return TwoByteOpcodeID(OP_SETCC + cond); +} + +enum GroupOpcodeID { + GROUP1_OP_ADD = 0, + GROUP1_OP_OR = 1, + GROUP1_OP_ADC = 2, + GROUP1_OP_AND = 4, + GROUP1_OP_SUB = 5, + GROUP1_OP_XOR = 6, + GROUP1_OP_CMP = 7, + + GROUP1A_OP_POP = 0, + + GROUP2_OP_SHL = 4, + GROUP2_OP_SHR = 5, + GROUP2_OP_SAR = 7, + + GROUP3_OP_TEST = 0, + GROUP3_OP_NOT = 2, + GROUP3_OP_NEG = 3, + GROUP3_OP_IMUL = 5, + GROUP3_OP_DIV = 6, + GROUP3_OP_IDIV = 7, + + GROUP5_OP_INC = 0, + GROUP5_OP_DEC = 1, + GROUP5_OP_CALLN = 2, + GROUP5_OP_JMPN = 4, + GROUP5_OP_PUSH = 6, + + FPU6_OP_FLD = 0, + FPU6_OP_FISTTP = 1, + FPU6_OP_FSTP = 3, + + GROUP11_MOV = 0 +}; + +static const RegisterID noBase = rbp; +static const RegisterID hasSib = rsp; +static const RegisterID noIndex = rsp; +#ifdef JS_CODEGEN_X64 +static const RegisterID noBase2 = r13; +static const RegisterID hasSib2 = r12; +#endif + +enum ModRmMode { + ModRmMemoryNoDisp, + ModRmMemoryDisp8, + ModRmMemoryDisp32, + ModRmRegister +}; + +} // namespace X86Encoding + +} // namespace jit +} // namespace js + +#endif /* jit_shared_Encoding_x86_shared_h */ diff --git a/js/src/jit/shared/MacroAssembler-x86-shared.h b/js/src/jit/shared/MacroAssembler-x86-shared.h index cb73ca6cbf3..b4462923153 100644 --- a/js/src/jit/shared/MacroAssembler-x86-shared.h +++ b/js/src/jit/shared/MacroAssembler-x86-shared.h @@ -213,17 +213,17 @@ class MacroAssemblerX86Shared : public Assembler } void atomic_cmpxchg8(Register newval, const Operand &addr, Register oldval_and_result) { // %eax must be explicitly provided for calling clarity. - MOZ_ASSERT(oldval_and_result.code() == X86Registers::eax); + MOZ_ASSERT(oldval_and_result.code() == X86Encoding::rax); lock_cmpxchg8(newval, addr); } void atomic_cmpxchg16(Register newval, const Operand &addr, Register oldval_and_result) { // %eax must be explicitly provided for calling clarity. - MOZ_ASSERT(oldval_and_result.code() == X86Registers::eax); + MOZ_ASSERT(oldval_and_result.code() == X86Encoding::rax); lock_cmpxchg16(newval, addr); } void atomic_cmpxchg32(Register newval, const Operand &addr, Register oldval_and_result) { // %eax must be explicitly provided for calling clarity. - MOZ_ASSERT(oldval_and_result.code() == X86Registers::eax); + MOZ_ASSERT(oldval_and_result.code() == X86Encoding::rax); lock_cmpxchg32(newval, addr); } diff --git a/js/src/jit/shared/Patching-x86-shared.h b/js/src/jit/shared/Patching-x86-shared.h new file mode 100644 index 00000000000..f7c065b36ae --- /dev/null +++ b/js/src/jit/shared/Patching-x86-shared.h @@ -0,0 +1,137 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef jit_shared_Patching_x86_shared_h +#define jit_shared_Patching_x86_shared_h + +namespace js { +namespace jit { + +namespace X86Encoding { + +inline void * +GetPointer(const void* where) +{ + return reinterpret_cast(where)[-1]; +} + +inline void ** +GetPointerRef(void* where) +{ + return &reinterpret_cast(where)[-1]; +} + +inline void +SetPointer(void* where, const void* value) +{ + reinterpret_cast(where)[-1] = value; +} + +inline int32_t +GetInt32(const void* where) +{ + return reinterpret_cast(where)[-1]; +} + +inline void +SetInt32(void* where, int32_t value) +{ + reinterpret_cast(where)[-1] = value; +} + +inline void +AddInt32(void* where, int32_t value) +{ +#ifdef DEBUG + uint32_t x = reinterpret_cast(where)[-1]; + uint32_t y = x + uint32_t(value); + MOZ_ASSERT(value >= 0 ? (int32_t(y) >= int32_t(x)) : (int32_t(y) < int32_t(x))); +#endif + reinterpret_cast(where)[-1] += uint32_t(value); +} + +inline void +SetRel32(void* from, void* to) +{ + intptr_t offset = reinterpret_cast(to) - reinterpret_cast(from); + MOZ_ASSERT(offset == static_cast(offset), + "offset is too great for a 32-bit relocation"); + if (offset != static_cast(offset)) + MOZ_CRASH("offset is too great for a 32-bit relocation"); + + SetInt32(from, offset); +} + +inline void * +GetRel32Target(void* where) +{ + int32_t rel = GetInt32(where); + return (char *)where + rel; +} + +class JmpSrc { + public: + JmpSrc() + : offset_(-1) + { + } + + explicit JmpSrc(int32_t offset) + : offset_(offset) + { + } + + int32_t offset() const { + return offset_; + } + + bool isSet() const { + return offset_ != -1; + } + + private: + int offset_; +}; + +class JmpDst { + public: + JmpDst() + : offset_(-1) + , used_(false) + { + } + + bool isUsed() const { return used_; } + void used() { used_ = true; } + bool isValid() const { return offset_ != -1; } + + explicit JmpDst(int32_t offset) + : offset_(offset) + , used_(false) + { + MOZ_ASSERT(offset_ == offset); + } + int32_t offset() const { + return offset_; + } + private: + int32_t offset_ : 31; + bool used_ : 1; +}; + +inline bool +CanRelinkJump(void* from, void* to) +{ + intptr_t offset = static_cast(to) - static_cast(from); + return (offset == static_cast(offset)); +} + +} // namespace X86Encoding + +} // namespace jit +} // namespace js + +#endif /* jit_shared_Patching_x86_shared_h */ diff --git a/js/src/jit/x64/Architecture-x64.h b/js/src/jit/x64/Architecture-x64.h index 8087501b3f8..3428503838a 100644 --- a/js/src/jit/x64/Architecture-x64.h +++ b/js/src/jit/x64/Architecture-x64.h @@ -7,7 +7,7 @@ #ifndef jit_x64_Architecture_x64_h #define jit_x64_Architecture_x64_h -#include "jit/shared/BaseAssembler-x86-shared.h" +#include "jit/shared/Constants-x86-shared.h" namespace js { namespace jit { @@ -26,7 +26,7 @@ static const uint32_t ShadowStackSpace = 0; class Registers { public: - typedef X86Registers::RegisterID Code; + typedef X86Encoding::RegisterID Code; typedef uint32_t SetType; static uint32_t SetSize(SetType x) { static_assert(sizeof(SetType) == 4, "SetType must be 32 bits"); @@ -54,8 +54,8 @@ class Registers { return Invalid; } - static const Code StackPointer = X86Registers::esp; - static const Code Invalid = X86Registers::invalid_reg; + static const Code StackPointer = X86Encoding::rsp; + static const Code Invalid = X86Encoding::invalid_reg; static const uint32_t Total = 16; static const uint32_t TotalPhys = 16; @@ -65,46 +65,46 @@ class Registers { static const uint32_t ArgRegMask = # if !defined(_WIN64) - (1 << X86Registers::edi) | - (1 << X86Registers::esi) | + (1 << X86Encoding::rdi) | + (1 << X86Encoding::rsi) | # endif - (1 << X86Registers::edx) | - (1 << X86Registers::ecx) | - (1 << X86Registers::r8) | - (1 << X86Registers::r9); + (1 << X86Encoding::rdx) | + (1 << X86Encoding::rcx) | + (1 << X86Encoding::r8) | + (1 << X86Encoding::r9); static const uint32_t VolatileMask = - (1 << X86Registers::eax) | - (1 << X86Registers::ecx) | - (1 << X86Registers::edx) | + (1 << X86Encoding::rax) | + (1 << X86Encoding::rcx) | + (1 << X86Encoding::rdx) | # if !defined(_WIN64) - (1 << X86Registers::esi) | - (1 << X86Registers::edi) | + (1 << X86Encoding::rsi) | + (1 << X86Encoding::rdi) | # endif - (1 << X86Registers::r8) | - (1 << X86Registers::r9) | - (1 << X86Registers::r10) | - (1 << X86Registers::r11); + (1 << X86Encoding::r8) | + (1 << X86Encoding::r9) | + (1 << X86Encoding::r10) | + (1 << X86Encoding::r11); static const uint32_t NonVolatileMask = - (1 << X86Registers::ebx) | + (1 << X86Encoding::rbx) | #if defined(_WIN64) - (1 << X86Registers::esi) | - (1 << X86Registers::edi) | + (1 << X86Encoding::rsi) | + (1 << X86Encoding::rdi) | #endif - (1 << X86Registers::ebp) | - (1 << X86Registers::r12) | - (1 << X86Registers::r13) | - (1 << X86Registers::r14) | - (1 << X86Registers::r15); + (1 << X86Encoding::rbp) | + (1 << X86Encoding::r12) | + (1 << X86Encoding::r13) | + (1 << X86Encoding::r14) | + (1 << X86Encoding::r15); static const uint32_t WrapperMask = VolatileMask; static const uint32_t SingleByteRegs = VolatileMask | NonVolatileMask; static const uint32_t NonAllocatableMask = - (1 << X86Registers::esp) | - (1 << X86Registers::r11); // This is ScratchReg. + (1 << X86Encoding::rsp) | + (1 << X86Encoding::r11); // This is ScratchReg. static const uint32_t AllocatableMask = AllMask & ~NonAllocatableMask; @@ -113,11 +113,11 @@ class Registers { // Registers returned from a JS -> JS call. static const uint32_t JSCallMask = - (1 << X86Registers::ecx); + (1 << X86Encoding::rcx); // Registers returned from a JS -> C call. static const uint32_t CallMask = - (1 << X86Registers::eax); + (1 << X86Encoding::rax); }; // Smallest integer type that can hold a register bitmask. @@ -125,14 +125,10 @@ typedef uint16_t PackedRegisterMask; class FloatRegisters { public: - typedef X86Registers::XMMRegisterID Code; + typedef X86Encoding::XMMRegisterID Code; typedef uint32_t SetType; static const char *GetName(Code code) { - static const char * const Names[] = { "xmm0", "xmm1", "xmm2", "xmm3", - "xmm4", "xmm5", "xmm6", "xmm7", - "xmm8", "xmm9", "xmm10", "xmm11", - "xmm12", "xmm13", "xmm14", "xmm15" }; - return Names[code]; + return X86Encoding::XMMRegName(code); } static Code FromName(const char *name) { @@ -143,7 +139,7 @@ class FloatRegisters { return Invalid; } - static const Code Invalid = X86Registers::invalid_xmm; + static const Code Invalid = X86Encoding::invalid_xmm; static const uint32_t Total = 16; static const uint32_t TotalPhys = 16; @@ -154,12 +150,12 @@ class FloatRegisters { static const uint32_t AllDoubleMask = AllMask; static const uint32_t VolatileMask = #if defined(_WIN64) - (1 << X86Registers::xmm0) | - (1 << X86Registers::xmm1) | - (1 << X86Registers::xmm2) | - (1 << X86Registers::xmm3) | - (1 << X86Registers::xmm4) | - (1 << X86Registers::xmm5); + (1 << X86Encoding::xmm0) | + (1 << X86Encoding::xmm1) | + (1 << X86Encoding::xmm2) | + (1 << X86Encoding::xmm3) | + (1 << X86Encoding::xmm4) | + (1 << X86Encoding::xmm5); #else AllMask; #endif @@ -169,7 +165,7 @@ class FloatRegisters { static const uint32_t WrapperMask = VolatileMask; static const uint32_t NonAllocatableMask = - (1 << X86Registers::xmm15); // This is ScratchDoubleReg. + (1 << X86Encoding::xmm15); // This is ScratchDoubleReg. static const uint32_t AllocatableMask = AllMask & ~NonAllocatableMask; }; diff --git a/js/src/jit/x64/Assembler-x64.cpp b/js/src/jit/x64/Assembler-x64.cpp index 9846f2b5b21..111bc1d10a1 100644 --- a/js/src/jit/x64/Assembler-x64.cpp +++ b/js/src/jit/x64/Assembler-x64.cpp @@ -101,11 +101,11 @@ ABIArgGenerator::next(MIRType type) } // Avoid r11, which is the MacroAssembler's ScratchReg. -const Register ABIArgGenerator::NonArgReturnReg0 = r10; -const Register ABIArgGenerator::NonArgReturnReg1 = r12; -const Register ABIArgGenerator::NonVolatileReg = r13; -const Register ABIArgGenerator::NonArg_VolatileReg = rax; -const Register ABIArgGenerator::NonReturn_VolatileReg0 = rcx; +const Register ABIArgGenerator::NonArgReturnReg0 = jit::r10; +const Register ABIArgGenerator::NonArgReturnReg1 = jit::r12; +const Register ABIArgGenerator::NonVolatileReg = jit::r13; +const Register ABIArgGenerator::NonArg_VolatileReg = jit::rax; +const Register ABIArgGenerator::NonReturn_VolatileReg0 = jit::rcx; void Assembler::writeRelocation(JmpSrc src, Relocation::Kind reloc) @@ -216,8 +216,8 @@ Assembler::executableCopy(uint8_t *buffer) // to jump to a different code block. continue; } - if (X86Assembler::canRelinkJump(src, rp.target)) { - X86Assembler::setRel32(src, rp.target); + if (X86Encoding::CanRelinkJump(src, rp.target)) { + X86Encoding::SetRel32(src, rp.target); } else { // An extended jump table must exist, and its offset must be in // range. @@ -226,11 +226,11 @@ Assembler::executableCopy(uint8_t *buffer) // Patch the jump to go to the extended jump entry. uint8_t *entry = buffer + extendedJumpTable_ + i * SizeOfJumpTableEntry; - X86Assembler::setRel32(src, entry); + X86Encoding::SetRel32(src, entry); // Now patch the pointer, note that we need to align it to // *after* the extended jump, i.e. after the 64-bit immedate. - X86Assembler::setPointer(entry + SizeOfExtendedJump, rp.target); + X86Encoding::SetPointer(entry + SizeOfExtendedJump, rp.target); } } } @@ -268,13 +268,13 @@ class RelocationIterator JitCode * Assembler::CodeFromJump(JitCode *code, uint8_t *jump) { - uint8_t *target = (uint8_t *)X86Assembler::getRel32Target(jump); + uint8_t *target = (uint8_t *)X86Encoding::GetRel32Target(jump); if (target >= code->raw() && target < code->raw() + code->instructionsSize()) { // This jump is within the code buffer, so it has been redirected to // the extended jump table. MOZ_ASSERT(target + SizeOfJumpTableEntry <= code->raw() + code->instructionsSize()); - target = (uint8_t *)X86Assembler::getPointer(target + SizeOfExtendedJump); + target = (uint8_t *)X86Encoding::GetPointer(target + SizeOfExtendedJump); } return JitCode::FromExecutable(target); diff --git a/js/src/jit/x64/Assembler-x64.h b/js/src/jit/x64/Assembler-x64.h index 9f9874d9790..f9c0bf0194e 100644 --- a/js/src/jit/x64/Assembler-x64.h +++ b/js/src/jit/x64/Assembler-x64.h @@ -16,39 +16,39 @@ namespace js { namespace jit { -static MOZ_CONSTEXPR_VAR Register rax = { X86Registers::eax }; -static MOZ_CONSTEXPR_VAR Register rbx = { X86Registers::ebx }; -static MOZ_CONSTEXPR_VAR Register rcx = { X86Registers::ecx }; -static MOZ_CONSTEXPR_VAR Register rdx = { X86Registers::edx }; -static MOZ_CONSTEXPR_VAR Register rsi = { X86Registers::esi }; -static MOZ_CONSTEXPR_VAR Register rdi = { X86Registers::edi }; -static MOZ_CONSTEXPR_VAR Register rbp = { X86Registers::ebp }; -static MOZ_CONSTEXPR_VAR Register r8 = { X86Registers::r8 }; -static MOZ_CONSTEXPR_VAR Register r9 = { X86Registers::r9 }; -static MOZ_CONSTEXPR_VAR Register r10 = { X86Registers::r10 }; -static MOZ_CONSTEXPR_VAR Register r11 = { X86Registers::r11 }; -static MOZ_CONSTEXPR_VAR Register r12 = { X86Registers::r12 }; -static MOZ_CONSTEXPR_VAR Register r13 = { X86Registers::r13 }; -static MOZ_CONSTEXPR_VAR Register r14 = { X86Registers::r14 }; -static MOZ_CONSTEXPR_VAR Register r15 = { X86Registers::r15 }; -static MOZ_CONSTEXPR_VAR Register rsp = { X86Registers::esp }; +static MOZ_CONSTEXPR_VAR Register rax = { X86Encoding::rax }; +static MOZ_CONSTEXPR_VAR Register rbx = { X86Encoding::rbx }; +static MOZ_CONSTEXPR_VAR Register rcx = { X86Encoding::rcx }; +static MOZ_CONSTEXPR_VAR Register rdx = { X86Encoding::rdx }; +static MOZ_CONSTEXPR_VAR Register rsi = { X86Encoding::rsi }; +static MOZ_CONSTEXPR_VAR Register rdi = { X86Encoding::rdi }; +static MOZ_CONSTEXPR_VAR Register rbp = { X86Encoding::rbp }; +static MOZ_CONSTEXPR_VAR Register r8 = { X86Encoding::r8 }; +static MOZ_CONSTEXPR_VAR Register r9 = { X86Encoding::r9 }; +static MOZ_CONSTEXPR_VAR Register r10 = { X86Encoding::r10 }; +static MOZ_CONSTEXPR_VAR Register r11 = { X86Encoding::r11 }; +static MOZ_CONSTEXPR_VAR Register r12 = { X86Encoding::r12 }; +static MOZ_CONSTEXPR_VAR Register r13 = { X86Encoding::r13 }; +static MOZ_CONSTEXPR_VAR Register r14 = { X86Encoding::r14 }; +static MOZ_CONSTEXPR_VAR Register r15 = { X86Encoding::r15 }; +static MOZ_CONSTEXPR_VAR Register rsp = { X86Encoding::rsp }; -static MOZ_CONSTEXPR_VAR FloatRegister xmm0 = { X86Registers::xmm0 }; -static MOZ_CONSTEXPR_VAR FloatRegister xmm1 = { X86Registers::xmm1 }; -static MOZ_CONSTEXPR_VAR FloatRegister xmm2 = { X86Registers::xmm2 }; -static MOZ_CONSTEXPR_VAR FloatRegister xmm3 = { X86Registers::xmm3 }; -static MOZ_CONSTEXPR_VAR FloatRegister xmm4 = { X86Registers::xmm4 }; -static MOZ_CONSTEXPR_VAR FloatRegister xmm5 = { X86Registers::xmm5 }; -static MOZ_CONSTEXPR_VAR FloatRegister xmm6 = { X86Registers::xmm6 }; -static MOZ_CONSTEXPR_VAR FloatRegister xmm7 = { X86Registers::xmm7 }; -static MOZ_CONSTEXPR_VAR FloatRegister xmm8 = { X86Registers::xmm8 }; -static MOZ_CONSTEXPR_VAR FloatRegister xmm9 = { X86Registers::xmm9 }; -static MOZ_CONSTEXPR_VAR FloatRegister xmm10 = { X86Registers::xmm10 }; -static MOZ_CONSTEXPR_VAR FloatRegister xmm11 = { X86Registers::xmm11 }; -static MOZ_CONSTEXPR_VAR FloatRegister xmm12 = { X86Registers::xmm12 }; -static MOZ_CONSTEXPR_VAR FloatRegister xmm13 = { X86Registers::xmm13 }; -static MOZ_CONSTEXPR_VAR FloatRegister xmm14 = { X86Registers::xmm14 }; -static MOZ_CONSTEXPR_VAR FloatRegister xmm15 = { X86Registers::xmm15 }; +static MOZ_CONSTEXPR_VAR FloatRegister xmm0 = { X86Encoding::xmm0 }; +static MOZ_CONSTEXPR_VAR FloatRegister xmm1 = { X86Encoding::xmm1 }; +static MOZ_CONSTEXPR_VAR FloatRegister xmm2 = { X86Encoding::xmm2 }; +static MOZ_CONSTEXPR_VAR FloatRegister xmm3 = { X86Encoding::xmm3 }; +static MOZ_CONSTEXPR_VAR FloatRegister xmm4 = { X86Encoding::xmm4 }; +static MOZ_CONSTEXPR_VAR FloatRegister xmm5 = { X86Encoding::xmm5 }; +static MOZ_CONSTEXPR_VAR FloatRegister xmm6 = { X86Encoding::xmm6 }; +static MOZ_CONSTEXPR_VAR FloatRegister xmm7 = { X86Encoding::xmm7 }; +static MOZ_CONSTEXPR_VAR FloatRegister xmm8 = { X86Encoding::xmm8 }; +static MOZ_CONSTEXPR_VAR FloatRegister xmm9 = { X86Encoding::xmm9 }; +static MOZ_CONSTEXPR_VAR FloatRegister xmm10 = { X86Encoding::xmm10 }; +static MOZ_CONSTEXPR_VAR FloatRegister xmm11 = { X86Encoding::xmm11 }; +static MOZ_CONSTEXPR_VAR FloatRegister xmm12 = { X86Encoding::xmm12 }; +static MOZ_CONSTEXPR_VAR FloatRegister xmm13 = { X86Encoding::xmm13 }; +static MOZ_CONSTEXPR_VAR FloatRegister xmm14 = { X86Encoding::xmm14 }; +static MOZ_CONSTEXPR_VAR FloatRegister xmm15 = { X86Encoding::xmm15 }; // X86-common synonyms. static MOZ_CONSTEXPR_VAR Register eax = rax; @@ -60,8 +60,8 @@ static MOZ_CONSTEXPR_VAR Register edi = rdi; static MOZ_CONSTEXPR_VAR Register ebp = rbp; static MOZ_CONSTEXPR_VAR Register esp = rsp; -static MOZ_CONSTEXPR_VAR Register InvalidReg = { X86Registers::invalid_reg }; -static MOZ_CONSTEXPR_VAR FloatRegister InvalidFloatReg = { X86Registers::invalid_xmm }; +static MOZ_CONSTEXPR_VAR Register InvalidReg = { X86Encoding::invalid_reg }; +static MOZ_CONSTEXPR_VAR FloatRegister InvalidFloatReg = { X86Encoding::invalid_xmm }; static MOZ_CONSTEXPR_VAR Register StackPointer = rsp; static MOZ_CONSTEXPR_VAR Register FramePointer = rbp; @@ -717,7 +717,7 @@ class Assembler : public AssemblerX86Shared } void j(Condition cond, ImmPtr target, Relocation::Kind reloc = Relocation::HARDCODED) { - JmpSrc src = masm.jCC(static_cast(cond)); + JmpSrc src = masm.jCC(static_cast(cond)); addPendingJump(src, target, reloc); } @@ -767,10 +767,10 @@ class Assembler : public AssemblerX86Shared static inline void PatchJump(CodeLocationJump jump, CodeLocationLabel label) { - if (X86Assembler::canRelinkJump(jump.raw(), label.raw())) { - X86Assembler::setRel32(jump.raw(), label.raw()); + if (X86Encoding::CanRelinkJump(jump.raw(), label.raw())) { + X86Encoding::SetRel32(jump.raw(), label.raw()); } else { - X86Assembler::setRel32(jump.raw(), jump.jumpTableEntry()); + X86Encoding::SetRel32(jump.raw(), jump.jumpTableEntry()); Assembler::PatchJumpEntry(jump.jumpTableEntry(), label.raw()); } } diff --git a/js/src/jit/x64/MacroAssembler-x64.h b/js/src/jit/x64/MacroAssembler-x64.h index 83f1d79f8d4..5083523cecc 100644 --- a/js/src/jit/x64/MacroAssembler-x64.h +++ b/js/src/jit/x64/MacroAssembler-x64.h @@ -635,7 +635,7 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared } void branch32(Condition cond, AbsoluteAddress lhs, Imm32 rhs, Label *label) { - if (X86Assembler::isAddressImmediate(lhs.addr)) { + if (X86Encoding::IsAddressImmediate(lhs.addr)) { branch32(cond, Operand(lhs), rhs, label); } else { mov(ImmPtr(lhs.addr), ScratchReg); @@ -647,7 +647,7 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared branch32(cond, Address(ScratchReg, 0), rhs, label); } void branch32(Condition cond, AbsoluteAddress lhs, Register rhs, Label *label) { - if (X86Assembler::isAddressImmediate(lhs.addr)) { + if (X86Encoding::IsAddressImmediate(lhs.addr)) { branch32(cond, Operand(lhs), rhs, label); } else { mov(ImmPtr(lhs.addr), ScratchReg); @@ -655,7 +655,7 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared } } void branchTest32(Condition cond, AbsoluteAddress address, Imm32 imm, Label *label) { - if (X86Assembler::isAddressImmediate(address.addr)) { + if (X86Encoding::IsAddressImmediate(address.addr)) { test32(Operand(address), imm); } else { mov(ImmPtr(address.addr), ScratchReg); @@ -667,7 +667,7 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared // Specialization for AbsoluteAddress. void branchPtr(Condition cond, AbsoluteAddress addr, Register ptr, Label *label) { MOZ_ASSERT(ptr != ScratchReg); - if (X86Assembler::isAddressImmediate(addr.addr)) { + if (X86Encoding::IsAddressImmediate(addr.addr)) { branchPtr(cond, Operand(addr), ptr, label); } else { mov(ImmPtr(addr.addr), ScratchReg); @@ -675,7 +675,7 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared } } void branchPtr(Condition cond, AbsoluteAddress addr, ImmWord ptr, Label *label) { - if (X86Assembler::isAddressImmediate(addr.addr)) { + if (X86Encoding::IsAddressImmediate(addr.addr)) { branchPtr(cond, Operand(addr), ptr, label); } else { mov(ImmPtr(addr.addr), ScratchReg); @@ -767,7 +767,7 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared movePtr(noteMaybeNurseryPtr(imm), dest); } void loadPtr(AbsoluteAddress address, Register dest) { - if (X86Assembler::isAddressImmediate(address.addr)) { + if (X86Encoding::IsAddressImmediate(address.addr)) { movq(Operand(address), dest); } else { mov(ImmPtr(address.addr), ScratchReg); @@ -788,7 +788,7 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared shlq(Imm32(1), dest); } void load32(AbsoluteAddress address, Register dest) { - if (X86Assembler::isAddressImmediate(address.addr)) { + if (X86Encoding::IsAddressImmediate(address.addr)) { movl(Operand(address), dest); } else { mov(ImmPtr(address.addr), ScratchReg); @@ -823,7 +823,7 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared movq(src, dest); } void storePtr(Register src, AbsoluteAddress address) { - if (X86Assembler::isAddressImmediate(address.addr)) { + if (X86Encoding::IsAddressImmediate(address.addr)) { movq(src, Operand(address)); } else { mov(ImmPtr(address.addr), ScratchReg); @@ -831,7 +831,7 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared } } void store32(Register src, AbsoluteAddress address) { - if (X86Assembler::isAddressImmediate(address.addr)) { + if (X86Encoding::IsAddressImmediate(address.addr)) { movl(src, Operand(address)); } else { mov(ImmPtr(address.addr), ScratchReg); @@ -1355,7 +1355,7 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared } void inc64(AbsoluteAddress dest) { - if (X86Assembler::isAddressImmediate(dest.addr)) { + if (X86Encoding::IsAddressImmediate(dest.addr)) { addPtr(Imm32(1), Operand(dest)); } else { mov(ImmPtr(dest.addr), ScratchReg); diff --git a/js/src/jit/x86/Architecture-x86.h b/js/src/jit/x86/Architecture-x86.h index 05e3a59fb4d..08c833cffef 100644 --- a/js/src/jit/x86/Architecture-x86.h +++ b/js/src/jit/x86/Architecture-x86.h @@ -7,7 +7,7 @@ #ifndef jit_x86_Architecture_x86_h #define jit_x86_Architecture_x86_h -#include "jit/shared/BaseAssembler-x86-shared.h" +#include "jit/shared/Constants-x86-shared.h" namespace js { namespace jit { @@ -35,7 +35,7 @@ static const uint32_t BAILOUT_TABLE_ENTRY_SIZE = 5; class Registers { public: - typedef X86Registers::RegisterID Code; + typedef X86Encoding::RegisterID Code; typedef uint8_t SetType; static uint32_t SetSize(SetType x) { static_assert(sizeof(SetType) == 1, "SetType must be 8 bits"); @@ -48,9 +48,7 @@ class Registers { return 31 - mozilla::CountLeadingZeroes32(x); } static const char *GetName(Code code) { - static const char * const Names[] = { "eax", "ecx", "edx", "ebx", - "esp", "ebp", "esi", "edi" }; - return Names[code]; + return X86Encoding::GPRegName(code); } static Code FromName(const char *name) { @@ -61,8 +59,8 @@ class Registers { return Invalid; } - static const Code StackPointer = X86Registers::esp; - static const Code Invalid = X86Registers::invalid_reg; + static const Code StackPointer = X86Encoding::rsp; + static const Code Invalid = X86Encoding::invalid_reg; static const uint32_t Total = 8; static const uint32_t TotalPhys = 8; @@ -73,28 +71,28 @@ class Registers { static const uint32_t ArgRegMask = 0; static const uint32_t VolatileMask = - (1 << X86Registers::eax) | - (1 << X86Registers::ecx) | - (1 << X86Registers::edx); + (1 << X86Encoding::rax) | + (1 << X86Encoding::rcx) | + (1 << X86Encoding::rdx); static const uint32_t NonVolatileMask = - (1 << X86Registers::ebx) | - (1 << X86Registers::esi) | - (1 << X86Registers::edi) | - (1 << X86Registers::ebp); + (1 << X86Encoding::rbx) | + (1 << X86Encoding::rsi) | + (1 << X86Encoding::rdi) | + (1 << X86Encoding::rbp); static const uint32_t WrapperMask = VolatileMask | - (1 << X86Registers::ebx); + (1 << X86Encoding::rbx); static const uint32_t SingleByteRegs = - (1 << X86Registers::eax) | - (1 << X86Registers::ecx) | - (1 << X86Registers::edx) | - (1 << X86Registers::ebx); + (1 << X86Encoding::rax) | + (1 << X86Encoding::rcx) | + (1 << X86Encoding::rdx) | + (1 << X86Encoding::rbx); static const uint32_t NonAllocatableMask = - (1 << X86Registers::esp); + (1 << X86Encoding::rsp); static const uint32_t AllocatableMask = AllMask & ~NonAllocatableMask; @@ -103,12 +101,12 @@ class Registers { // Registers returned from a JS -> JS call. static const uint32_t JSCallMask = - (1 << X86Registers::ecx) | - (1 << X86Registers::edx); + (1 << X86Encoding::rcx) | + (1 << X86Encoding::rdx); // Registers returned from a JS -> C call. static const uint32_t CallMask = - (1 << X86Registers::eax); + (1 << X86Encoding::rax); }; // Smallest integer type that can hold a register bitmask. @@ -116,12 +114,10 @@ typedef uint8_t PackedRegisterMask; class FloatRegisters { public: - typedef X86Registers::XMMRegisterID Code; + typedef X86Encoding::XMMRegisterID Code; typedef uint32_t SetType; static const char *GetName(Code code) { - static const char * const Names[] = { "xmm0", "xmm1", "xmm2", "xmm3", - "xmm4", "xmm5", "xmm6", "xmm7" }; - return Names[code]; + return X86Encoding::XMMRegName(code); } static Code FromName(const char *name) { @@ -132,7 +128,7 @@ class FloatRegisters { return Invalid; } - static const Code Invalid = X86Registers::invalid_xmm; + static const Code Invalid = X86Encoding::invalid_xmm; static const uint32_t Total = 8; static const uint32_t TotalPhys = 8; @@ -146,7 +142,7 @@ class FloatRegisters { static const uint32_t WrapperMask = VolatileMask; static const uint32_t NonAllocatableMask = - (1 << X86Registers::xmm7); // This is ScratchDoubleReg. + (1 << X86Encoding::xmm7); // This is ScratchDoubleReg. static const uint32_t AllocatableMask = AllMask & ~NonAllocatableMask; }; diff --git a/js/src/jit/x86/Assembler-x86.cpp b/js/src/jit/x86/Assembler-x86.cpp index 8b6812f0d9b..8a4f40f8f10 100644 --- a/js/src/jit/x86/Assembler-x86.cpp +++ b/js/src/jit/x86/Assembler-x86.cpp @@ -58,7 +58,7 @@ Assembler::executableCopy(uint8_t *buffer) for (size_t i = 0; i < jumps_.length(); i++) { RelativePatch &rp = jumps_[i]; - X86Assembler::setRel32(buffer + rp.offset, rp.target); + X86Encoding::SetRel32(buffer + rp.offset, rp.target); } } @@ -87,7 +87,7 @@ class RelocationIterator static inline JitCode * CodeFromJump(uint8_t *jump) { - uint8_t *target = (uint8_t *)X86Assembler::getRel32Target(jump); + uint8_t *target = (uint8_t *)X86Encoding::GetRel32Target(jump); return JitCode::FromExecutable(target); } diff --git a/js/src/jit/x86/Assembler-x86.h b/js/src/jit/x86/Assembler-x86.h index 8aeb92022bd..27ce2e5eff6 100644 --- a/js/src/jit/x86/Assembler-x86.h +++ b/js/src/jit/x86/Assembler-x86.h @@ -13,31 +13,31 @@ #include "jit/IonCode.h" #include "jit/JitCompartment.h" #include "jit/shared/Assembler-shared.h" -#include "jit/shared/BaseAssembler-x86-shared.h" +#include "jit/shared/Constants-x86-shared.h" namespace js { namespace jit { -static MOZ_CONSTEXPR_VAR Register eax = { X86Registers::eax }; -static MOZ_CONSTEXPR_VAR Register ecx = { X86Registers::ecx }; -static MOZ_CONSTEXPR_VAR Register edx = { X86Registers::edx }; -static MOZ_CONSTEXPR_VAR Register ebx = { X86Registers::ebx }; -static MOZ_CONSTEXPR_VAR Register esp = { X86Registers::esp }; -static MOZ_CONSTEXPR_VAR Register ebp = { X86Registers::ebp }; -static MOZ_CONSTEXPR_VAR Register esi = { X86Registers::esi }; -static MOZ_CONSTEXPR_VAR Register edi = { X86Registers::edi }; +static MOZ_CONSTEXPR_VAR Register eax = { X86Encoding::rax }; +static MOZ_CONSTEXPR_VAR Register ecx = { X86Encoding::rcx }; +static MOZ_CONSTEXPR_VAR Register edx = { X86Encoding::rdx }; +static MOZ_CONSTEXPR_VAR Register ebx = { X86Encoding::rbx }; +static MOZ_CONSTEXPR_VAR Register esp = { X86Encoding::rsp }; +static MOZ_CONSTEXPR_VAR Register ebp = { X86Encoding::rbp }; +static MOZ_CONSTEXPR_VAR Register esi = { X86Encoding::rsi }; +static MOZ_CONSTEXPR_VAR Register edi = { X86Encoding::rdi }; -static MOZ_CONSTEXPR_VAR FloatRegister xmm0 = { X86Registers::xmm0 }; -static MOZ_CONSTEXPR_VAR FloatRegister xmm1 = { X86Registers::xmm1 }; -static MOZ_CONSTEXPR_VAR FloatRegister xmm2 = { X86Registers::xmm2 }; -static MOZ_CONSTEXPR_VAR FloatRegister xmm3 = { X86Registers::xmm3 }; -static MOZ_CONSTEXPR_VAR FloatRegister xmm4 = { X86Registers::xmm4 }; -static MOZ_CONSTEXPR_VAR FloatRegister xmm5 = { X86Registers::xmm5 }; -static MOZ_CONSTEXPR_VAR FloatRegister xmm6 = { X86Registers::xmm6 }; -static MOZ_CONSTEXPR_VAR FloatRegister xmm7 = { X86Registers::xmm7 }; +static MOZ_CONSTEXPR_VAR FloatRegister xmm0 = { X86Encoding::xmm0 }; +static MOZ_CONSTEXPR_VAR FloatRegister xmm1 = { X86Encoding::xmm1 }; +static MOZ_CONSTEXPR_VAR FloatRegister xmm2 = { X86Encoding::xmm2 }; +static MOZ_CONSTEXPR_VAR FloatRegister xmm3 = { X86Encoding::xmm3 }; +static MOZ_CONSTEXPR_VAR FloatRegister xmm4 = { X86Encoding::xmm4 }; +static MOZ_CONSTEXPR_VAR FloatRegister xmm5 = { X86Encoding::xmm5 }; +static MOZ_CONSTEXPR_VAR FloatRegister xmm6 = { X86Encoding::xmm6 }; +static MOZ_CONSTEXPR_VAR FloatRegister xmm7 = { X86Encoding::xmm7 }; -static MOZ_CONSTEXPR_VAR Register InvalidReg = { X86Registers::invalid_reg }; -static MOZ_CONSTEXPR_VAR FloatRegister InvalidFloatReg = { X86Registers::invalid_xmm }; +static MOZ_CONSTEXPR_VAR Register InvalidReg = { X86Encoding::invalid_reg }; +static MOZ_CONSTEXPR_VAR FloatRegister InvalidFloatReg = { X86Encoding::invalid_xmm }; static MOZ_CONSTEXPR_VAR Register JSReturnReg_Type = ecx; static MOZ_CONSTEXPR_VAR Register JSReturnReg_Data = edx; @@ -164,7 +164,7 @@ PatchJump(CodeLocationJump jump, CodeLocationLabel label) MOZ_ASSERT(((*x >= 0x80 && *x <= 0x8F) && *(x - 1) == 0x0F) || (*x == 0xE9)); #endif - X86Assembler::setRel32(jump.raw(), label.raw()); + X86Encoding::SetRel32(jump.raw(), label.raw()); } static inline void PatchBackedge(CodeLocationJump &jump_, CodeLocationLabel label, JitRuntime::BackedgeTarget target) @@ -381,7 +381,7 @@ class Assembler : public AssemblerX86Shared } void j(Condition cond, ImmPtr target, Relocation::Kind reloc = Relocation::HARDCODED) { - JmpSrc src = masm.jCC(static_cast(cond)); + JmpSrc src = masm.jCC(static_cast(cond)); addPendingJump(src, target, reloc); } @@ -423,9 +423,9 @@ class Assembler : public AssemblerX86Shared void retarget(Label *label, ImmPtr target, Relocation::Kind reloc) { if (label->used()) { bool more; - X86Assembler::JmpSrc jmp(label->offset()); + X86Encoding::JmpSrc jmp(label->offset()); do { - X86Assembler::JmpSrc next; + X86Encoding::JmpSrc next; more = masm.nextJump(jmp, &next); addPendingJump(jmp, target, reloc); jmp = next; @@ -630,7 +630,7 @@ class Assembler : public AssemblerX86Shared } static bool canUseInSingleByteInstruction(Register reg) { - return !ByteRegRequiresRex(reg.code()); + return X86Encoding::HasSubregL(reg.code()); } }; From 9c84568ec4706a2c60b0e7bb96ba419db20a89fb Mon Sep 17 00:00:00 2001 From: Tom Schuster Date: Sat, 31 Jan 2015 01:11:24 +0100 Subject: [PATCH 073/101] Bug 1100936 - Handle various operations on revoked proxies. r=efaust --- .../jit-test/tests/proxy/function-toString.js | 10 ++++++ .../tests/proxy/operations-on-revoked.js | 18 +++++++++++ js/src/proxy/ScriptedDirectProxyHandler.cpp | 32 +++++++++++++++++++ js/src/proxy/ScriptedDirectProxyHandler.h | 5 +++ 4 files changed, 65 insertions(+) create mode 100644 js/src/jit-test/tests/proxy/function-toString.js create mode 100644 js/src/jit-test/tests/proxy/operations-on-revoked.js diff --git a/js/src/jit-test/tests/proxy/function-toString.js b/js/src/jit-test/tests/proxy/function-toString.js new file mode 100644 index 00000000000..cedcf552a41 --- /dev/null +++ b/js/src/jit-test/tests/proxy/function-toString.js @@ -0,0 +1,10 @@ +load(libdir + 'asserts.js'); + +// Function.prototype.toString doesn't accept ES6 proxies. + +var proxy = new Proxy(function() {}, {}); +assertThrowsInstanceOf(() => Function.prototype.toString.call(proxy), TypeError); +var o = Proxy.revocable(function() {}, {}); +assertThrowsInstanceOf(() => Function.prototype.toString.call(o.proxy), TypeError); +o.revoke(); +assertThrowsInstanceOf(() => Function.prototype.toString.call(o.proxy), TypeError); diff --git a/js/src/jit-test/tests/proxy/operations-on-revoked.js b/js/src/jit-test/tests/proxy/operations-on-revoked.js new file mode 100644 index 00000000000..98514d112de --- /dev/null +++ b/js/src/jit-test/tests/proxy/operations-on-revoked.js @@ -0,0 +1,18 @@ +load(libdir + 'asserts.js'); + +var r = Proxy.revocable({}, {}); +var r2 = Proxy.revocable(function(){}, {}); +r.revoke(); +r2.revoke(); + +var p = r.proxy; +var p2 = r2.proxy; + +assertThrowsInstanceOf(() => ({} instanceof p), TypeError); +assertThrowsInstanceOf(() => ({} instanceof p2), TypeError); + +assertEq(Object.prototype.toString.call(p), "[object Object]"); +assertEq(Object.prototype.toString.call(p2), "[object Function]"); + +assertThrowsInstanceOf(() => RegExp.prototype.exec.call(p, ""), TypeError); +assertThrowsInstanceOf(() => RegExp.prototype.exec.call(p2, ""), TypeError); diff --git a/js/src/proxy/ScriptedDirectProxyHandler.cpp b/js/src/proxy/ScriptedDirectProxyHandler.cpp index dccfc07d63f..6b9e17d25b5 100644 --- a/js/src/proxy/ScriptedDirectProxyHandler.cpp +++ b/js/src/proxy/ScriptedDirectProxyHandler.cpp @@ -1102,6 +1102,19 @@ ScriptedDirectProxyHandler::nativeCall(JSContext *cx, IsAcceptableThis test, Nat return false; } +bool +ScriptedDirectProxyHandler::hasInstance(JSContext *cx, HandleObject proxy, MutableHandleValue v, + bool *bp) const +{ + RootedObject handler(cx, GetDirectProxyHandlerObject(proxy)); + if (!handler) { + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_PROXY_REVOKED); + return false; + } + + return DirectProxyHandler::hasInstance(cx, proxy, v, bp); +} + bool ScriptedDirectProxyHandler::objectClassIs(HandleObject proxy, ESClassValue classValue, JSContext *cx) const @@ -1122,6 +1135,25 @@ ScriptedDirectProxyHandler::objectClassIs(HandleObject proxy, ESClassValue class return IsArray(target, cx); } +const char * +ScriptedDirectProxyHandler::className(JSContext *cx, HandleObject proxy) const +{ + // Right now the caller is not prepared to handle failures. + RootedObject handler(cx, GetDirectProxyHandlerObject(proxy)); + if (!handler) + return BaseProxyHandler::className(cx, proxy); + + return DirectProxyHandler::className(cx, proxy); +} +JSString * +ScriptedDirectProxyHandler::fun_toString(JSContext *cx, HandleObject proxy, + unsigned indent) const +{ + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO, + js_Function_str, js_toString_str, "object"); + return nullptr; +} + bool ScriptedDirectProxyHandler::regexp_toShared(JSContext *cx, HandleObject proxy, RegExpGuard *g) const diff --git a/js/src/proxy/ScriptedDirectProxyHandler.h b/js/src/proxy/ScriptedDirectProxyHandler.h index bb53401626a..f2e560ef15f 100644 --- a/js/src/proxy/ScriptedDirectProxyHandler.h +++ b/js/src/proxy/ScriptedDirectProxyHandler.h @@ -67,8 +67,13 @@ class ScriptedDirectProxyHandler : public DirectProxyHandler { // A scripted proxy should not be treated as generic in most contexts. virtual bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl, CallArgs args) const MOZ_OVERRIDE; + virtual bool hasInstance(JSContext *cx, HandleObject proxy, MutableHandleValue v, + bool *bp) const MOZ_OVERRIDE; virtual bool objectClassIs(HandleObject obj, ESClassValue classValue, JSContext *cx) const MOZ_OVERRIDE; + virtual const char *className(JSContext *cx, HandleObject proxy) const MOZ_OVERRIDE; + virtual JSString *fun_toString(JSContext *cx, HandleObject proxy, + unsigned indent) const MOZ_OVERRIDE; virtual bool regexp_toShared(JSContext *cx, HandleObject proxy, RegExpGuard *g) const MOZ_OVERRIDE; virtual bool boxedValue_unbox(JSContext *cx, HandleObject proxy, From 1fafe747080ff80f3963724828cb86b71b0fbcac Mon Sep 17 00:00:00 2001 From: JW Wang Date: Sat, 31 Jan 2015 13:22:12 +1300 Subject: [PATCH 074/101] Bug 1121332. Part 1 - add media key status to gmp-api. r=cpearce. --- dom/media/eme/CDMCallbackProxy.cpp | 29 ++------- dom/media/eme/CDMCallbackProxy.h | 8 +-- dom/media/eme/CDMCaps.cpp | 62 +++++++++---------- dom/media/eme/CDMCaps.h | 27 ++++---- dom/media/gmp-plugin/fake.info | 2 +- dom/media/gmp/GMPDecryptorChild.cpp | 26 +++----- dom/media/gmp/GMPDecryptorChild.h | 14 ++--- dom/media/gmp/GMPDecryptorParent.cpp | 19 ++---- dom/media/gmp/GMPDecryptorParent.h | 8 +-- dom/media/gmp/GMPDecryptorProxy.h | 8 +-- dom/media/gmp/GMPMessageUtils.h | 7 +++ dom/media/gmp/PGMPDecryptor.ipdl | 6 +- dom/media/gmp/gmp-api/gmp-decryption.h | 28 +++++---- dom/media/gtest/TestGMPCrossOrigin.cpp | 7 +-- .../0.1/ClearKeySessionManager.cpp | 15 +++-- media/gmp-clearkey/0.1/clearkey.info | 2 +- 16 files changed, 114 insertions(+), 154 deletions(-) diff --git a/dom/media/eme/CDMCallbackProxy.cpp b/dom/media/eme/CDMCallbackProxy.cpp index e8570b6d303..78ab8a9bd78 100644 --- a/dom/media/eme/CDMCallbackProxy.cpp +++ b/dom/media/eme/CDMCallbackProxy.cpp @@ -267,35 +267,18 @@ CDMCallbackProxy::SessionError(const nsCString& aSessionId, } void -CDMCallbackProxy::KeyIdUsable(const nsCString& aSessionId, - const nsTArray& aKeyId) +CDMCallbackProxy::KeyStatusChanged(const nsCString& aSessionId, + const nsTArray& aKeyId, + GMPMediaKeyStatus aStatus) { MOZ_ASSERT(mProxy->IsOnGMPThread()); bool keysChange = false; { CDMCaps::AutoLock caps(mProxy->Capabilites()); - keysChange = caps.SetKeyUsable(aKeyId, NS_ConvertUTF8toUTF16(aSessionId)); - } - if (keysChange) { - nsRefPtr task; - task = NS_NewRunnableMethodWithArg(mProxy, - &CDMProxy::OnKeysChange, - NS_ConvertUTF8toUTF16(aSessionId)); - NS_DispatchToMainThread(task); - } -} - -void -CDMCallbackProxy::KeyIdNotUsable(const nsCString& aSessionId, - const nsTArray& aKeyId) -{ - MOZ_ASSERT(mProxy->IsOnGMPThread()); - - bool keysChange = false; - { - CDMCaps::AutoLock caps(mProxy->Capabilites()); - keysChange = caps.SetKeyUnusable(aKeyId, NS_ConvertUTF8toUTF16(aSessionId)); + keysChange = caps.SetKeyStatus(aKeyId, + NS_ConvertUTF8toUTF16(aSessionId), + aStatus); } if (keysChange) { nsRefPtr task; diff --git a/dom/media/eme/CDMCallbackProxy.h b/dom/media/eme/CDMCallbackProxy.h index 47a94abf454..6f41743ad60 100644 --- a/dom/media/eme/CDMCallbackProxy.h +++ b/dom/media/eme/CDMCallbackProxy.h @@ -43,11 +43,9 @@ public: uint32_t aSystemCode, const nsCString& aMessage) MOZ_OVERRIDE; - virtual void KeyIdUsable(const nsCString& aSessionId, - const nsTArray& aKeyId) MOZ_OVERRIDE; - - virtual void KeyIdNotUsable(const nsCString& aSessionId, - const nsTArray& aKeyId) MOZ_OVERRIDE; + virtual void KeyStatusChanged(const nsCString& aSessionId, + const nsTArray& aKeyId, + GMPMediaKeyStatus aStatus) MOZ_OVERRIDE; virtual void SetCaps(uint64_t aCaps) MOZ_OVERRIDE; diff --git a/dom/media/eme/CDMCaps.cpp b/dom/media/eme/CDMCaps.cpp index e86cccbc1a6..16f7ed486c9 100644 --- a/dom/media/eme/CDMCaps.cpp +++ b/dom/media/eme/CDMCaps.cpp @@ -5,7 +5,6 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "CDMCaps.h" -#include "gmp-decryption.h" #include "EMELog.h" #include "nsThreadUtils.h" #include "SamplesWaitingForKey.h" @@ -80,9 +79,9 @@ bool CDMCaps::AutoLock::IsKeyUsable(const CencKeyId& aKeyId) { mData.mMonitor.AssertCurrentThreadOwns(); - const auto& keys = mData.mUsableKeyIds; + const auto& keys = mData.mKeyStatuses; for (size_t i = 0; i < keys.Length(); i++) { - if (keys[i].mId == aKeyId) { + if (keys[i].mId == aKeyId && keys[i].mStatus == kGMPUsable) { return true; } } @@ -90,15 +89,32 @@ CDMCaps::AutoLock::IsKeyUsable(const CencKeyId& aKeyId) } bool -CDMCaps::AutoLock::SetKeyUsable(const CencKeyId& aKeyId, - const nsString& aSessionId) +CDMCaps::AutoLock::SetKeyStatus(const CencKeyId& aKeyId, + const nsString& aSessionId, + GMPMediaKeyStatus aStatus) { mData.mMonitor.AssertCurrentThreadOwns(); - UsableKey key(aKeyId, aSessionId); - if (mData.mUsableKeyIds.Contains(key)) { - return false; + KeyStatus key(aKeyId, aSessionId, aStatus); + auto index = mData.mKeyStatuses.IndexOf(key); + + if (aStatus == kGMPUnknown) { + // Return true if the element is found to notify key changes. + return mData.mKeyStatuses.RemoveElement(key); } - mData.mUsableKeyIds.AppendElement(key); + + if (index != mData.mKeyStatuses.NoIndex) { + if (mData.mKeyStatuses[index].mStatus == aStatus) { + return false; + } + mData.mKeyStatuses[index].mStatus = aStatus; + } else { + mData.mKeyStatuses.AppendElement(key); + } + + if (aStatus != kGMPUsable) { + return true; + } + auto& waiters = mData.mWaitForKeys; size_t i = 0; while (i < waiters.Length()) { @@ -113,26 +129,6 @@ CDMCaps::AutoLock::SetKeyUsable(const CencKeyId& aKeyId, return true; } -bool -CDMCaps::AutoLock::SetKeyUnusable(const CencKeyId& aKeyId, - const nsString& aSessionId) -{ - mData.mMonitor.AssertCurrentThreadOwns(); - UsableKey key(aKeyId, aSessionId); - if (!mData.mUsableKeyIds.Contains(key)) { - return false; - } - auto& keys = mData.mUsableKeyIds; - for (size_t i = 0; i < keys.Length(); i++) { - if (keys[i].mId == aKeyId && - keys[i].mSessionId == aSessionId) { - keys.RemoveElementAt(i); - break; - } - } - return true; -} - void CDMCaps::AutoLock::NotifyWhenKeyIdUsable(const CencKeyId& aKey, SamplesWaitingForKey* aListener) @@ -178,12 +174,12 @@ void CDMCaps::AutoLock::GetUsableKeysForSession(const nsAString& aSessionId, nsTArray& aOutKeyIds) { - for (size_t i = 0; i < mData.mUsableKeyIds.Length(); i++) { - const auto& key = mData.mUsableKeyIds[i]; - if (key.mSessionId.Equals(aSessionId)) { + for (size_t i = 0; i < mData.mKeyStatuses.Length(); i++) { + const auto& key = mData.mKeyStatuses[i]; + if (key.mSessionId.Equals(aSessionId) && key.mStatus == kGMPUsable) { aOutKeyIds.AppendElement(key.mId); } } } -} // namespace mozilla \ No newline at end of file +} // namespace mozilla diff --git a/dom/media/eme/CDMCaps.h b/dom/media/eme/CDMCaps.h index 5e2415f8b78..cd34348ab51 100644 --- a/dom/media/eme/CDMCaps.h +++ b/dom/media/eme/CDMCaps.h @@ -14,6 +14,7 @@ #include "nsTArray.h" #include "mozilla/Attributes.h" #include "SamplesWaitingForKey.h" +#include "gmp-decryption.h" namespace mozilla { @@ -37,13 +38,9 @@ public: bool IsKeyUsable(const CencKeyId& aKeyId); - // Returns true if setting this key usable results in the usable keys - // changing for this session, i.e. the key was not previously marked usable. - bool SetKeyUsable(const CencKeyId& aKeyId, const nsString& aSessionId); - - // Returns true if setting this key unusable results in the usable keys - // changing for this session, i.e. the key was previously marked usable. - bool SetKeyUnusable(const CencKeyId& aKeyId, const nsString& aSessionId); + // Returns true if key status changed, + // i.e. the key status changed from usable to expired. + bool SetKeyStatus(const CencKeyId& aKeyId, const nsString& aSessionId, GMPMediaKeyStatus aStatus); void GetUsableKeysForSession(const nsAString& aSessionId, nsTArray& aOutKeyIds); @@ -85,25 +82,29 @@ private: Monitor mMonitor; - struct UsableKey { - UsableKey(const CencKeyId& aId, - const nsString& aSessionId) + struct KeyStatus { + KeyStatus(const CencKeyId& aId, + const nsString& aSessionId, + GMPMediaKeyStatus aStatus) : mId(aId) , mSessionId(aSessionId) + , mStatus(aStatus) {} - UsableKey(const UsableKey& aOther) + KeyStatus(const KeyStatus& aOther) : mId(aOther.mId) , mSessionId(aOther.mSessionId) + , mStatus(aOther.mStatus) {} - bool operator==(const UsableKey& aOther) const { + bool operator==(const KeyStatus& aOther) const { return mId == aOther.mId && mSessionId == aOther.mSessionId; }; CencKeyId mId; nsString mSessionId; + GMPMediaKeyStatus mStatus; }; - nsTArray mUsableKeyIds; + nsTArray mKeyStatuses; nsTArray mWaitForKeys; diff --git a/dom/media/gmp-plugin/fake.info b/dom/media/gmp-plugin/fake.info index 5b29387b6b7..58624893c0c 100644 --- a/dom/media/gmp-plugin/fake.info +++ b/dom/media/gmp-plugin/fake.info @@ -1,5 +1,5 @@ Name: fake Description: Fake GMP Plugin Version: 1.0 -APIs: encode-video[h264], decode-video[h264], eme-decrypt-v4[fake] +APIs: encode-video[h264], decode-video[h264], eme-decrypt-v5[fake] Libraries: dxva2.dll diff --git a/dom/media/gmp/GMPDecryptorChild.cpp b/dom/media/gmp/GMPDecryptorChild.cpp index b8cf43da7cf..4c17065b6f9 100644 --- a/dom/media/gmp/GMPDecryptorChild.cpp +++ b/dom/media/gmp/GMPDecryptorChild.cpp @@ -126,27 +126,17 @@ GMPDecryptorChild::SessionError(const char* aSessionId, } void -GMPDecryptorChild::KeyIdUsable(const char* aSessionId, - uint32_t aSessionIdLength, - const uint8_t* aKeyId, - uint32_t aKeyIdLength) +GMPDecryptorChild::KeyStatusChanged(const char* aSessionId, + uint32_t aSessionIdLength, + const uint8_t* aKeyId, + uint32_t aKeyIdLength, + GMPMediaKeyStatus aStatus) { nsAutoTArray kid; kid.AppendElements(aKeyId, aKeyIdLength); - CALL_ON_GMP_THREAD(SendKeyIdUsable, - nsAutoCString(aSessionId, aSessionIdLength), kid); -} - -void -GMPDecryptorChild::KeyIdNotUsable(const char* aSessionId, - uint32_t aSessionIdLength, - const uint8_t* aKeyId, - uint32_t aKeyIdLength) -{ - nsAutoTArray kid; - kid.AppendElements(aKeyId, aKeyIdLength); - CALL_ON_GMP_THREAD(SendKeyIdNotUsable, - nsAutoCString(aSessionId, aSessionIdLength), kid); + CALL_ON_GMP_THREAD(SendKeyStatusChanged, + nsAutoCString(aSessionId, aSessionIdLength), kid, + aStatus); } void diff --git a/dom/media/gmp/GMPDecryptorChild.h b/dom/media/gmp/GMPDecryptorChild.h index ee1f72c327f..f175ece6985 100644 --- a/dom/media/gmp/GMPDecryptorChild.h +++ b/dom/media/gmp/GMPDecryptorChild.h @@ -63,15 +63,11 @@ public: const char* aMessage, uint32_t aMessageLength) MOZ_OVERRIDE; - virtual void KeyIdUsable(const char* aSessionId, - uint32_t aSessionIdLength, - const uint8_t* aKeyId, - uint32_t aKeyIdLength) MOZ_OVERRIDE; - - virtual void KeyIdNotUsable(const char* aSessionId, - uint32_t aSessionIdLength, - const uint8_t* aKeyId, - uint32_t aKeyIdLength) MOZ_OVERRIDE; + virtual void KeyStatusChanged(const char* aSessionId, + uint32_t aSessionIdLength, + const uint8_t* aKeyId, + uint32_t aKeyIdLength, + GMPMediaKeyStatus aStatus) MOZ_OVERRIDE; virtual void SetCapabilities(uint64_t aCaps) MOZ_OVERRIDE; diff --git a/dom/media/gmp/GMPDecryptorParent.cpp b/dom/media/gmp/GMPDecryptorParent.cpp index 946687b786e..b87e70b1962 100644 --- a/dom/media/gmp/GMPDecryptorParent.cpp +++ b/dom/media/gmp/GMPDecryptorParent.cpp @@ -265,26 +265,15 @@ GMPDecryptorParent::RecvSessionError(const nsCString& aSessionId, } bool -GMPDecryptorParent::RecvKeyIdUsable(const nsCString& aSessionId, - InfallibleTArray&& aKeyId) +GMPDecryptorParent::RecvKeyStatusChanged(const nsCString& aSessionId, + InfallibleTArray&& aKeyId, + const GMPMediaKeyStatus& aStatus) { if (!mIsOpen) { NS_WARNING("Trying to use a dead GMP decrypter!"); return false; } - mCallback->KeyIdUsable(aSessionId, aKeyId); - return true; -} - -bool -GMPDecryptorParent::RecvKeyIdNotUsable(const nsCString& aSessionId, - InfallibleTArray&& aKeyId) -{ - if (!mIsOpen) { - NS_WARNING("Trying to use a dead GMP decrypter!"); - return false; - } - mCallback->KeyIdNotUsable(aSessionId, aKeyId); + mCallback->KeyStatusChanged(aSessionId, aKeyId, aStatus); return true; } diff --git a/dom/media/gmp/GMPDecryptorParent.h b/dom/media/gmp/GMPDecryptorParent.h index b61e6e87e62..cb1fb173b2c 100644 --- a/dom/media/gmp/GMPDecryptorParent.h +++ b/dom/media/gmp/GMPDecryptorParent.h @@ -91,11 +91,9 @@ private: const uint32_t& aSystemCode, const nsCString& aMessage) MOZ_OVERRIDE; - virtual bool RecvKeyIdUsable(const nsCString& aSessionId, - InfallibleTArray&& aKeyId) MOZ_OVERRIDE; - - virtual bool RecvKeyIdNotUsable(const nsCString& aSessionId, - InfallibleTArray&& aKeyId) MOZ_OVERRIDE; + virtual bool RecvKeyStatusChanged(const nsCString& aSessionId, + InfallibleTArray&& aKeyId, + const GMPMediaKeyStatus& aStatus) MOZ_OVERRIDE; virtual bool RecvDecrypted(const uint32_t& aId, const GMPErr& aErr, diff --git a/dom/media/gmp/GMPDecryptorProxy.h b/dom/media/gmp/GMPDecryptorProxy.h index 1e4e2c682d7..b3916e5a338 100644 --- a/dom/media/gmp/GMPDecryptorProxy.h +++ b/dom/media/gmp/GMPDecryptorProxy.h @@ -44,11 +44,9 @@ public: uint32_t aSystemCode, const nsCString& aMessage) = 0; - virtual void KeyIdUsable(const nsCString& aSessionId, - const nsTArray& aKeyId) = 0; - - virtual void KeyIdNotUsable(const nsCString& aSessionId, - const nsTArray& aKeyId) = 0; + virtual void KeyStatusChanged(const nsCString& aSessionId, + const nsTArray& aKeyId, + GMPMediaKeyStatus aStatus) = 0; virtual void SetCaps(uint64_t aCaps) = 0; diff --git a/dom/media/gmp/GMPMessageUtils.h b/dom/media/gmp/GMPMessageUtils.h index 0b381713fb9..471f928e5e6 100644 --- a/dom/media/gmp/GMPMessageUtils.h +++ b/dom/media/gmp/GMPMessageUtils.h @@ -60,6 +60,13 @@ struct ParamTraits kGMPMessageInvalid> {}; +template <> +struct ParamTraits +: public ContiguousEnumSerializer +{}; + template <> struct ParamTraits : public ContiguousEnumSerializer& aKeyId) MOZ_OVERRIDE { } - virtual void KeyIdNotUsable(const nsCString& aSessionId, - const nsTArray& aKeyId) MOZ_OVERRIDE {} + virtual void KeyStatusChanged(const nsCString& aSessionId, + const nsTArray& aKeyId, + GMPMediaKeyStatus aStatus) MOZ_OVERRIDE { } virtual void SetCaps(uint64_t aCaps) MOZ_OVERRIDE {} virtual void Decrypted(uint32_t aId, GMPErr aResult, diff --git a/media/gmp-clearkey/0.1/ClearKeySessionManager.cpp b/media/gmp-clearkey/0.1/ClearKeySessionManager.cpp index 4d67aac2ae4..4d86fe121f2 100644 --- a/media/gmp-clearkey/0.1/ClearKeySessionManager.cpp +++ b/media/gmp-clearkey/0.1/ClearKeySessionManager.cpp @@ -167,8 +167,9 @@ ClearKeySessionManager::PersistentSessionDataLoaded(GMPErr aStatus, mDecryptionManager->ExpectKeyId(keyId); mDecryptionManager->InitKey(keyId, key); mKeyIds.insert(key); - mCallback->KeyIdUsable(&aSessionId[0], aSessionId.size(), - &keyId[0], keyId.size()); + mCallback->KeyStatusChanged(&aSessionId[0], aSessionId.size(), + &keyId[0], keyId.size(), + kGMPUsable); } mCallback->ResolveLoadSessionPromise(aPromiseId, true); @@ -203,8 +204,9 @@ ClearKeySessionManager::UpdateSession(uint32_t aPromiseId, for (auto it = keyPairs.begin(); it != keyPairs.end(); it++) { mDecryptionManager->InitKey(it->mKeyId, it->mKey); mKeyIds.insert(it->mKeyId); - mCallback->KeyIdUsable(aSessionId, aSessionIdLength, - &it->mKeyId[0], it->mKeyId.size()); + mCallback->KeyStatusChanged(aSessionId, aSessionIdLength, + &it->mKeyId[0], it->mKeyId.size(), + kGMPUsable); } if (session->Type() != kGMPPersistentSession) { @@ -279,8 +281,9 @@ ClearKeySessionManager::ClearInMemorySessionData(ClearKeySession* aSession) mDecryptionManager->ReleaseKeyId(*it); const string& sessionId = aSession->Id(); - mCallback->KeyIdNotUsable(&sessionId[0], sessionId.size(), - &(*it)[0], it->size()); + mCallback->KeyStatusChanged(&sessionId[0], sessionId.size(), + &(*it)[0], it->size(), + kGMPUnknown); } mSessions.erase(aSession->Id()); diff --git a/media/gmp-clearkey/0.1/clearkey.info b/media/gmp-clearkey/0.1/clearkey.info index 639fe84ee38..feb0df92431 100644 --- a/media/gmp-clearkey/0.1/clearkey.info +++ b/media/gmp-clearkey/0.1/clearkey.info @@ -1,4 +1,4 @@ Name: clearkey Description: ClearKey decrypt-only GMP plugin Version: 0.1 -APIs: eme-decrypt-v4[org.w3.clearkey] +APIs: eme-decrypt-v5[org.w3.clearkey] From 378e98ed3ef3a30e5e85283ccbc4e9c688a05fa9 Mon Sep 17 00:00:00 2001 From: JW Wang Date: Sat, 31 Jan 2015 13:22:25 +1300 Subject: [PATCH 075/101] Bug 1121332. Part 2 - expose media key status from CDMCaps. r=cpearce. --- dom/media/eme/CDMCaps.cpp | 8 +++---- dom/media/eme/CDMCaps.h | 49 ++++++++++++++++++++------------------- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/dom/media/eme/CDMCaps.cpp b/dom/media/eme/CDMCaps.cpp index 16f7ed486c9..9882116450a 100644 --- a/dom/media/eme/CDMCaps.cpp +++ b/dom/media/eme/CDMCaps.cpp @@ -171,13 +171,13 @@ CDMCaps::AutoLock::CanDecryptVideo() } void -CDMCaps::AutoLock::GetUsableKeysForSession(const nsAString& aSessionId, - nsTArray& aOutKeyIds) +CDMCaps::AutoLock::GetKeyStatusesForSession(const nsAString& aSessionId, + nsTArray& aOutKeyStatuses) { for (size_t i = 0; i < mData.mKeyStatuses.Length(); i++) { const auto& key = mData.mKeyStatuses[i]; - if (key.mSessionId.Equals(aSessionId) && key.mStatus == kGMPUsable) { - aOutKeyIds.AppendElement(key.mId); + if (key.mSessionId.Equals(aSessionId)) { + aOutKeyStatuses.AppendElement(key); } } } diff --git a/dom/media/eme/CDMCaps.h b/dom/media/eme/CDMCaps.h index cd34348ab51..07c390f1f1a 100644 --- a/dom/media/eme/CDMCaps.h +++ b/dom/media/eme/CDMCaps.h @@ -25,6 +25,29 @@ public: CDMCaps(); ~CDMCaps(); + struct KeyStatus { + KeyStatus(const CencKeyId& aId, + const nsString& aSessionId, + GMPMediaKeyStatus aStatus) + : mId(aId) + , mSessionId(aSessionId) + , mStatus(aStatus) + {} + KeyStatus(const KeyStatus& aOther) + : mId(aOther.mId) + , mSessionId(aOther.mSessionId) + , mStatus(aOther.mStatus) + {} + bool operator==(const KeyStatus& aOther) const { + return mId == aOther.mId && + mSessionId == aOther.mSessionId; + }; + + CencKeyId mId; + nsString mSessionId; + GMPMediaKeyStatus mStatus; + }; + // Locks the CDMCaps. It must be locked to access its shared state. // Threadsafe when locked. class MOZ_STACK_CLASS AutoLock { @@ -42,8 +65,8 @@ public: // i.e. the key status changed from usable to expired. bool SetKeyStatus(const CencKeyId& aKeyId, const nsString& aSessionId, GMPMediaKeyStatus aStatus); - void GetUsableKeysForSession(const nsAString& aSessionId, - nsTArray& aOutKeyIds); + void GetKeyStatusesForSession(const nsAString& aSessionId, + nsTArray& aOutKeyStatuses); // Sets the capabilities of the CDM. aCaps is the logical OR of the // GMP_EME_CAP_* flags from gmp-decryption.h. @@ -82,28 +105,6 @@ private: Monitor mMonitor; - struct KeyStatus { - KeyStatus(const CencKeyId& aId, - const nsString& aSessionId, - GMPMediaKeyStatus aStatus) - : mId(aId) - , mSessionId(aSessionId) - , mStatus(aStatus) - {} - KeyStatus(const KeyStatus& aOther) - : mId(aOther.mId) - , mSessionId(aOther.mSessionId) - , mStatus(aOther.mStatus) - {} - bool operator==(const KeyStatus& aOther) const { - return mId == aOther.mId && - mSessionId == aOther.mSessionId; - }; - - CencKeyId mId; - nsString mSessionId; - GMPMediaKeyStatus mStatus; - }; nsTArray mKeyStatuses; nsTArray mWaitForKeys; From ec8a28c4ab9cb64a880a842d1376ece155d13cb6 Mon Sep 17 00:00:00 2001 From: JW Wang Date: Sat, 31 Jan 2015 13:22:48 +1300 Subject: [PATCH 076/101] Bug 1121332. Part 3 - export MapObject from JS. r=jorendorff. --- js/src/builtin/MapObject.cpp | 202 +++++++++++++++++++++++++++++------ js/src/builtin/MapObject.h | 10 +- js/src/jsapi.h | 31 ++++++ 3 files changed, 207 insertions(+), 36 deletions(-) diff --git a/js/src/builtin/MapObject.cpp b/js/src/builtin/MapObject.cpp index 147457b6784..9c2a5c82c50 100644 --- a/js/src/builtin/MapObject.cpp +++ b/js/src/builtin/MapObject.cpp @@ -1305,11 +1305,24 @@ MapObject::is(HandleValue v) return v.isObject() && v.toObject().hasClass(&class_) && v.toObject().as().getPrivate(); } +bool +MapObject::is(HandleObject o) +{ + return o->hasClass(&class_) && o->as().getPrivate(); +} + #define ARG0_KEY(cx, args, key) \ AutoHashableValueRooter key(cx); \ if (args.length() > 0 && !key.setValue(cx, args[0])) \ return false +ValueMap & +MapObject::extract(HandleObject o) +{ + MOZ_ASSERT(o->hasClass(&MapObject::class_)); + return *o->as().getData(); +} + ValueMap & MapObject::extract(CallReceiver call) { @@ -1318,15 +1331,21 @@ MapObject::extract(CallReceiver call) return *call.thisv().toObject().as().getData(); } +uint32_t +MapObject::size(JSContext *cx, HandleObject obj) +{ + MOZ_ASSERT(MapObject::is(obj)); + ValueMap &map = extract(obj); + static_assert(sizeof(map.count()) <= sizeof(uint32_t), + "map count must be precisely representable as a JS number"); + return map.count(); +} + bool MapObject::size_impl(JSContext *cx, CallArgs args) { - MOZ_ASSERT(MapObject::is(args.thisv())); - - ValueMap &map = extract(args); - static_assert(sizeof(map.count()) <= sizeof(uint32_t), - "map count must be precisely representable as a JS number"); - args.rval().setNumber(map.count()); + RootedObject obj(cx, &args.thisv().toObject()); + args.rval().setNumber(size(cx, obj)); return true; } @@ -1337,19 +1356,31 @@ MapObject::size(JSContext *cx, unsigned argc, Value *vp) return CallNonGenericMethod(cx, args); } +bool +MapObject::get(JSContext *cx, HandleObject obj, + HandleValue key, MutableHandleValue rval) +{ + MOZ_ASSERT(MapObject::is(obj)); + + ValueMap &map = extract(obj); + AutoHashableValueRooter k(cx); + + if (!k.setValue(cx, key)) + return false; + + if (ValueMap::Entry *p = map.get(k)) + rval.set(p->value); + else + rval.setUndefined(); + + return true; +} + bool MapObject::get_impl(JSContext *cx, CallArgs args) { - MOZ_ASSERT(MapObject::is(args.thisv())); - - ValueMap &map = extract(args); - ARG0_KEY(cx, args, key); - - if (ValueMap::Entry *p = map.get(key)) - args.rval().set(p->value); - else - args.rval().setUndefined(); - return true; + RootedObject obj(cx, &args.thisv().toObject()); + return get(cx, obj, args.get(0), args.rval()); } bool @@ -1359,15 +1390,31 @@ MapObject::get(JSContext *cx, unsigned argc, Value *vp) return CallNonGenericMethod(cx, args); } +bool +MapObject::has(JSContext *cx, HandleObject obj, HandleValue key, bool *rval) +{ + MOZ_ASSERT(MapObject::is(obj)); + + ValueMap &map = extract(obj); + AutoHashableValueRooter k(cx); + + if (!k.setValue(cx, key)) + return false; + + *rval = map.has(k); + return true; +} + bool MapObject::has_impl(JSContext *cx, CallArgs args) { - MOZ_ASSERT(MapObject::is(args.thisv())); - - ValueMap &map = extract(args); - ARG0_KEY(cx, args, key); - args.rval().setBoolean(map.has(key)); - return true; + bool found; + RootedObject obj(cx, &args.thisv().toObject()); + if (has(cx, obj, args.get(0), &found)) { + args.rval().setBoolean(found); + return true; + } + return false; } bool @@ -1433,16 +1480,21 @@ MapObject::delete_(JSContext *cx, unsigned argc, Value *vp) return CallNonGenericMethod(cx, args); } +bool +MapObject::iterator(JSContext *cx, IteratorKind kind, + HandleObject obj, MutableHandleValue iter) +{ + MOZ_ASSERT(MapObject::is(obj)); + ValueMap &map = extract(obj); + Rooted iterobj(cx, MapIteratorObject::create(cx, obj, &map, kind)); + return iterobj && (iter.setObject(*iterobj), true); +} + bool MapObject::iterator_impl(JSContext *cx, CallArgs args, IteratorKind kind) { - Rooted mapobj(cx, &args.thisv().toObject().as()); - ValueMap &map = *mapobj->getData(); - Rooted iterobj(cx, MapIteratorObject::create(cx, mapobj, &map, kind)); - if (!iterobj) - return false; - args.rval().setObject(*iterobj); - return true; + RootedObject obj(cx, &args.thisv().toObject()); + return iterator(cx, kind, obj, args.rval()); } bool @@ -1487,13 +1539,9 @@ MapObject::entries(JSContext *cx, unsigned argc, Value *vp) bool MapObject::clear_impl(JSContext *cx, CallArgs args) { - Rooted mapobj(cx, &args.thisv().toObject().as()); - if (!mapobj->getData()->clear()) { - js_ReportOutOfMemory(cx); - return false; - } + RootedObject obj(cx, &args.thisv().toObject()); args.rval().setUndefined(); - return true; + return clear(cx, obj); } bool @@ -1503,6 +1551,18 @@ MapObject::clear(JSContext *cx, unsigned argc, Value *vp) return CallNonGenericMethod(cx, is, clear_impl, args); } +bool +MapObject::clear(JSContext *cx, HandleObject obj) +{ + MOZ_ASSERT(MapObject::is(obj)); + ValueMap &map = extract(obj); + if (!map.clear()) { + js_ReportOutOfMemory(cx); + return false; + } + return true; +} + JSObject * js_InitMapClass(JSContext *cx, HandleObject obj) { @@ -2033,3 +2093,75 @@ js::InitSelfHostingCollectionIteratorFunctions(JSContext *cx, HandleObject obj) { return JS_DefineFunctions(cx, obj, selfhosting_collection_iterator_methods); } + +/*** JS public APIs **********************************************************/ + +JS_PUBLIC_API(JSObject *) +JS::NewMapObject(JSContext *cx) +{ + return MapObject::create(cx); +} + +JS_PUBLIC_API(uint32_t) +JS::MapSize(JSContext *cx, HandleObject obj) +{ + CHECK_REQUEST(cx); + return MapObject::size(cx, obj); +} + +JS_PUBLIC_API(bool) +JS::MapGet(JSContext *cx, HandleObject obj, + HandleValue key, MutableHandleValue rval) +{ + CHECK_REQUEST(cx); + assertSameCompartment(cx, key, rval); + return MapObject::get(cx, obj, key, rval); +} + +JS_PUBLIC_API(bool) +JS::MapHas(JSContext *cx, HandleObject obj, HandleValue key, bool *rval) +{ + CHECK_REQUEST(cx); + assertSameCompartment(cx, key); + return MapObject::has(cx, obj, key, rval); +} + +JS_PUBLIC_API(bool) +JS::MapSet(JSContext *cx, HandleObject obj, + HandleValue key, HandleValue val) +{ + CHECK_REQUEST(cx); + assertSameCompartment(cx, key, val); + return MapObject::set(cx, obj, key, val); +} + +JS_PUBLIC_API(bool) +JS::MapClear(JSContext *cx, HandleObject obj) +{ + CHECK_REQUEST(cx); + return MapObject::clear(cx, obj); +} + +JS_PUBLIC_API(bool) +JS::MapKeys(JSContext *cx, HandleObject obj, MutableHandleValue rval) +{ + CHECK_REQUEST(cx); + assertSameCompartment(cx, rval); + return MapObject::iterator(cx, MapObject::Keys, obj, rval); +} + +JS_PUBLIC_API(bool) +JS::MapValues(JSContext *cx, HandleObject obj, MutableHandleValue rval) +{ + CHECK_REQUEST(cx); + assertSameCompartment(cx, rval); + return MapObject::iterator(cx, MapObject::Values, obj, rval); +} + +JS_PUBLIC_API(bool) +JS::MapEntries(JSContext *cx, HandleObject obj, MutableHandleValue rval) +{ + CHECK_REQUEST(cx); + assertSameCompartment(cx, rval); + return MapObject::iterator(cx, MapObject::Entries, obj, rval); +} diff --git a/js/src/builtin/MapObject.h b/js/src/builtin/MapObject.h index 108bb2e2b27..c68784b7ba7 100644 --- a/js/src/builtin/MapObject.h +++ b/js/src/builtin/MapObject.h @@ -95,20 +95,28 @@ class MapObject : public NativeObject { static bool getKeysAndValuesInterleaved(JSContext *cx, HandleObject obj, JS::AutoValueVector *entries); static bool entries(JSContext *cx, unsigned argc, Value *vp); - static bool set(JSContext *cx, HandleObject obj, HandleValue key, HandleValue value); static bool has(JSContext *cx, unsigned argc, Value *vp); static MapObject* create(JSContext *cx); + static uint32_t size(JSContext *cx, HandleObject obj); + static bool get(JSContext *cx, HandleObject obj, HandleValue key, MutableHandleValue rval); + static bool has(JSContext *cx, HandleObject obj, HandleValue key, bool* rval); + static bool set(JSContext *cx, HandleObject obj, HandleValue key, HandleValue val); + static bool clear(JSContext *cx, HandleObject obj); + static bool iterator(JSContext *cx, IteratorKind kind, HandleObject obj, MutableHandleValue iter); + private: static const JSPropertySpec properties[]; static const JSFunctionSpec methods[]; ValueMap *getData() { return static_cast(getPrivate()); } + static ValueMap & extract(HandleObject o); static ValueMap & extract(CallReceiver call); static void mark(JSTracer *trc, JSObject *obj); static void finalize(FreeOp *fop, JSObject *obj); static bool construct(JSContext *cx, unsigned argc, Value *vp); static bool is(HandleValue v); + static bool is(HandleObject o); static bool iterator_impl(JSContext *cx, CallArgs args, IteratorKind kind); diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 8969694317e..55b1e065199 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -4558,6 +4558,37 @@ extern JS_PUBLIC_API(bool) SetWeakMapEntry(JSContext *cx, JS::HandleObject mapObj, JS::HandleObject key, JS::HandleValue val); +/* + * Map + */ +extern JS_PUBLIC_API(JSObject *) +NewMapObject(JSContext *cx); + +extern JS_PUBLIC_API(uint32_t) +MapSize(JSContext *cx, HandleObject obj); + +extern JS_PUBLIC_API(bool) +MapGet(JSContext *cx, HandleObject obj, + HandleValue key, MutableHandleValue rval); + +extern JS_PUBLIC_API(bool) +MapHas(JSContext *cx, HandleObject obj, HandleValue key, bool *rval); + +extern JS_PUBLIC_API(bool) +MapSet(JSContext *cx, HandleObject obj, HandleValue key, HandleValue val); + +extern JS_PUBLIC_API(bool) +MapClear(JSContext *cx, HandleObject obj); + +extern JS_PUBLIC_API(bool) +MapKeys(JSContext *cx, HandleObject obj, MutableHandleValue rval); + +extern JS_PUBLIC_API(bool) +MapValues(JSContext *cx, HandleObject obj, MutableHandleValue rval); + +extern JS_PUBLIC_API(bool) +MapEntries(JSContext *cx, HandleObject obj, MutableHandleValue rval); + } /* namespace JS */ /* From 78309fd6a6785b0518832c066b6a37605bb3455e Mon Sep 17 00:00:00 2001 From: JW Wang Date: Sat, 31 Jan 2015 13:23:07 +1300 Subject: [PATCH 077/101] Bug 1121332. Part 4 - implement MediaKeySession.keyStatuses and remove MediaKeySession.getUsableKeyIds. r=bz. --- dom/bindings/Bindings.conf | 4 + dom/media/eme/MediaKeySession.cpp | 58 +++---- dom/media/eme/MediaKeySession.h | 8 +- dom/media/eme/MediaKeyStatusMap.cpp | 245 ++++++++++++++++++++++++++++ dom/media/eme/MediaKeyStatusMap.h | 78 +++++++++ dom/media/eme/moz.build | 2 + dom/webidl/MediaKeySession.webidl | 9 +- dom/webidl/MediaKeyStatusMap.webidl | 36 ++++ dom/webidl/moz.build | 1 + 9 files changed, 406 insertions(+), 35 deletions(-) create mode 100644 dom/media/eme/MediaKeyStatusMap.cpp create mode 100644 dom/media/eme/MediaKeyStatusMap.h create mode 100644 dom/webidl/MediaKeyStatusMap.webidl diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf index 3d557012887..83426870251 100644 --- a/dom/bindings/Bindings.conf +++ b/dom/bindings/Bindings.conf @@ -633,6 +633,10 @@ DOMInterfaces = { 'headerFile': 'nsIMediaList.h', }, +'MediaKeyStatusMap' : { + 'implicitJSContext': [ 'size', 'get', 'has' ] +}, + 'MediaStream': { 'headerFile': 'DOMMediaStream.h', 'nativeType': 'mozilla::DOMMediaStream' diff --git a/dom/media/eme/MediaKeySession.cpp b/dom/media/eme/MediaKeySession.cpp index 37fe931e867..7dd803ce49d 100644 --- a/dom/media/eme/MediaKeySession.cpp +++ b/dom/media/eme/MediaKeySession.cpp @@ -9,6 +9,7 @@ #include "mozilla/dom/MediaKeyError.h" #include "mozilla/dom/MediaKeyMessageEvent.h" #include "mozilla/dom/MediaEncryptedEvent.h" +#include "mozilla/dom/MediaKeyStatusMap.h" #include "nsCycleCollectionParticipant.h" #include "mozilla/CDMProxy.h" #include "mozilla/AsyncEventDispatcher.h" @@ -22,6 +23,7 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED(MediaKeySession, DOMEventTargetHelper, mMediaKeyError, mKeys, + mKeyStatusMap, mClosed) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MediaKeySession) @@ -46,6 +48,7 @@ MediaKeySession::MediaKeySession(nsPIDOMWindow* aParent, , mToken(sMediaKeySessionNum++) , mIsClosed(false) , mUninitialized(true) + , mKeyStatusMap(new MediaKeyStatusMap(aParent)) { MOZ_ASSERT(aParent); mClosed = mKeys->MakePromise(aRv); @@ -106,6 +109,30 @@ MediaKeySession::Closed() const return mClosed; } + +void +MediaKeySession::UpdateKeyStatusMap() +{ + MOZ_ASSERT(!IsClosed()); + if (!mKeys->GetCDMProxy()) { + return; + } + + nsTArray keyStatuses; + { + CDMCaps::AutoLock caps(mKeys->GetCDMProxy()->Capabilites()); + caps.GetKeyStatusesForSession(mSessionId, keyStatuses); + } + + mKeyStatusMap->Update(keyStatuses); +} + +MediaKeyStatusMap* +MediaKeySession::KeyStatuses() const +{ + return mKeyStatusMap; +} + already_AddRefed MediaKeySession::GenerateRequest(const nsAString& aInitDataType, const ArrayBufferViewOrArrayBuffer& aInitData, @@ -248,34 +275,6 @@ MediaKeySession::Remove(ErrorResult& aRv) return promise.forget(); } -already_AddRefed -MediaKeySession::GetUsableKeyIds(ErrorResult& aRv) -{ - nsRefPtr promise(mKeys->MakePromise(aRv)); - if (aRv.Failed()) { - return nullptr; - } - - if (IsClosed() || !mKeys->GetCDMProxy()) { - promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); - return promise.forget(); - } - - nsTArray keyIds; - { - CDMCaps::AutoLock caps(mKeys->GetCDMProxy()->Capabilites()); - caps.GetUsableKeysForSession(mSessionId, keyIds); - } - - nsTArray> array; - for (size_t i = 0; i < keyIds.Length(); i++) { - array.AppendElement(keyIds[i]); - } - promise->MaybeResolve(array); - - return promise.forget(); -} - void MediaKeySession::DispatchKeyMessage(MediaKeyMessageType aMessageType, const nsTArray& aMessage) @@ -302,6 +301,9 @@ MediaKeySession::DispatchKeysChange() if (IsClosed()) { return; } + + UpdateKeyStatusMap(); + nsRefPtr asyncDispatcher = new AsyncEventDispatcher(this, NS_LITERAL_STRING("keyschange"), false); asyncDispatcher->PostDOMEvent(); diff --git a/dom/media/eme/MediaKeySession.h b/dom/media/eme/MediaKeySession.h index 2feb03f16cc..b384fa2e89a 100644 --- a/dom/media/eme/MediaKeySession.h +++ b/dom/media/eme/MediaKeySession.h @@ -30,6 +30,7 @@ namespace dom { class ArrayBufferViewOrArrayBuffer; class MediaKeyError; +class MediaKeyStatusMap; class MediaKeySession MOZ_FINAL : public DOMEventTargetHelper { @@ -51,6 +52,8 @@ public: // Mark this as resultNotAddRefed to return raw pointers MediaKeyError* GetError() const; + MediaKeyStatusMap* KeyStatuses() const; + void GetKeySystem(nsString& aRetval) const; void GetSessionId(nsString& aRetval) const; @@ -78,8 +81,6 @@ public: already_AddRefed Remove(ErrorResult& aRv); - already_AddRefed GetUsableKeyIds(ErrorResult& aRv); - void DispatchKeyMessage(MediaKeyMessageType aMessageType, const nsTArray& aMessage); @@ -97,6 +98,8 @@ public: private: ~MediaKeySession(); + void UpdateKeyStatusMap(); + nsRefPtr mClosed; nsRefPtr mMediaKeyError; @@ -107,6 +110,7 @@ private: const uint32_t mToken; bool mIsClosed; bool mUninitialized; + nsRefPtr mKeyStatusMap; }; } // namespace dom diff --git a/dom/media/eme/MediaKeyStatusMap.cpp b/dom/media/eme/MediaKeyStatusMap.cpp new file mode 100644 index 00000000000..30bfb9a6842 --- /dev/null +++ b/dom/media/eme/MediaKeyStatusMap.cpp @@ -0,0 +1,245 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsPIDOMWindow.h" +#include "mozilla/dom/MediaKeyStatusMap.h" +#include "mozilla/dom/UnionTypes.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaKeyStatusMap) +NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaKeyStatusMap) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaKeyStatusMap) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTION_CLASS(MediaKeyStatusMap) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MediaKeyStatusMap) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent) + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER + tmp->mMap = nullptr; +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(MediaKeyStatusMap) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(MediaKeyStatusMap) + NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mMap) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +MediaKeyStatusMap::MediaKeyStatusMap(nsPIDOMWindow* aParent) + : mParent(aParent) + , mUpdateError(NS_OK) +{ + AutoJSAPI jsapi; + if (NS_WARN_IF(!jsapi.Init(aParent))) { + mUpdateError = NS_ERROR_FAILURE; + return; + } + + JSContext* cx = jsapi.cx(); + mMap = JS::NewMapObject(cx); + if (NS_WARN_IF(!mMap)) { + mUpdateError = NS_ERROR_FAILURE; + } +} + +MediaKeyStatusMap::~MediaKeyStatusMap() +{ +} + +JSObject* +MediaKeyStatusMap::WrapObject(JSContext* aCx) +{ + return MediaKeyStatusMapBinding::Wrap(aCx, this); +} + +nsPIDOMWindow* +MediaKeyStatusMap::GetParentObject() const +{ + return mParent; +} + +MediaKeyStatus +MediaKeyStatusMap::Get(JSContext* aCx, + const ArrayBufferViewOrArrayBuffer& aKey, + ErrorResult& aRv) const +{ + if (NS_FAILED(mUpdateError)) { + aRv.Throw(mUpdateError); + return MediaKeyStatus::Internal_error; + } + + JS::Rooted map(aCx, mMap); + JS::Rooted key(aCx); + JS::Rooted val(aCx); + + if (!aKey.ToJSVal(aCx, map, &key) || + !JS::MapGet(aCx, map, key, &val)) { + aRv.Throw(NS_ERROR_FAILURE); + return MediaKeyStatus::Internal_error; + } + + bool ok; + int index = FindEnumStringIndex( + aCx, val, MediaKeyStatusValues::strings, + "MediaKeyStatus", "Invalid MediaKeyStatus value", &ok); + + return ok ? static_cast(index) : + MediaKeyStatus::Internal_error; +} + +bool +MediaKeyStatusMap::Has(JSContext* aCx, + const ArrayBufferViewOrArrayBuffer& aKey, + ErrorResult& aRv) const +{ + if (NS_FAILED(mUpdateError)) { + aRv.Throw(mUpdateError); + return false; + } + + JS::Rooted map(aCx, mMap); + JS::Rooted key(aCx); + bool result = false; + + if (!aKey.ToJSVal(aCx, map, &key) || + !JS::MapHas(aCx, map, key, &result)) { + aRv.Throw(NS_ERROR_FAILURE); + } + + return result; +} + +template +static void CallMapMethod(JSContext* aCx, + const JS::Heap& aMap, + JS::MutableHandle aResult, + ErrorResult& aRv, + nsresult aUpdateError) +{ + if (NS_FAILED(aUpdateError)) { + aRv.Throw(aUpdateError); + return; + } + + JS::Rooted map(aCx, aMap); + JS::Rooted result(aCx); + if (!Method(aCx, map, &result)) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + + aResult.set(&result.toObject()); +} + +void +MediaKeyStatusMap::Keys(JSContext* aCx, + JS::MutableHandle aResult, + ErrorResult& aRv) const +{ + CallMapMethod(aCx, mMap, aResult, aRv, mUpdateError); +} + +void +MediaKeyStatusMap::Values(JSContext* aCx, + JS::MutableHandle aResult, + ErrorResult& aRv) const +{ + CallMapMethod(aCx, mMap, aResult, aRv, mUpdateError); +} +void +MediaKeyStatusMap::Entries(JSContext* aCx, + JS::MutableHandle aResult, + ErrorResult& aRv) const +{ + CallMapMethod(aCx, mMap, aResult, aRv, mUpdateError); +} + +uint32_t +MediaKeyStatusMap::GetSize(JSContext* aCx, ErrorResult& aRv) const +{ + if (NS_FAILED(mUpdateError)) { + aRv.Throw(mUpdateError); + return 0; + } + JS::Rooted map(aCx, mMap); + return JS::MapSize(aCx, map); +} + +static MediaKeyStatus +ToMediaKeyStatus(GMPMediaKeyStatus aStatus) { + switch (aStatus) { + case kGMPUsable: return MediaKeyStatus::Usable; + case kGMPExpired: return MediaKeyStatus::Expired; + case kGMPOutputNotAllowed: return MediaKeyStatus::Output_not_allowed; + default: return MediaKeyStatus::Internal_error; + } +} + +static bool +ToJSString(JSContext* aCx, GMPMediaKeyStatus aStatus, + JS::MutableHandle aResult) +{ + auto val = uint32_t(ToMediaKeyStatus(aStatus)); + MOZ_ASSERT(val < ArrayLength(MediaKeyStatusValues::strings)); + JSString* str = JS_NewStringCopyN(aCx, + MediaKeyStatusValues::strings[val].value, + MediaKeyStatusValues::strings[val].length); + if (!str) { + return false; + } + aResult.setString(str); + return true; +} + +nsresult +MediaKeyStatusMap::UpdateInternal(const nsTArray& keys) +{ + AutoJSAPI jsapi; + if (!mMap || NS_WARN_IF(!jsapi.Init(mParent))) { + return NS_ERROR_FAILURE; + } + + jsapi.TakeOwnershipOfErrorReporting(); + JSContext* cx = jsapi.cx(); + JS::Rooted map(cx, mMap); + if (!JS::MapClear(cx, map)) { + return NS_ERROR_FAILURE; + } + + for (size_t i = 0; i < keys.Length(); i++) { + const auto& ks = keys[i]; + JS::Rooted key(cx); + JS::Rooted val(cx); + if (!ToJSValue(cx, TypedArrayCreator(ks.mId), &key) || + !ToJSString(cx, ks.mStatus, &val) || + !JS::MapSet(cx, map, key, val)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + return NS_OK; +} + +void +MediaKeyStatusMap::Update(const nsTArray& keys) +{ + // Since we can't leave the map in a partial update state, we need + // to remember the error and throw it next time the interface methods + // are called. + mUpdateError = UpdateInternal(keys); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/media/eme/MediaKeyStatusMap.h b/dom/media/eme/MediaKeyStatusMap.h new file mode 100644 index 00000000000..879e33b7600 --- /dev/null +++ b/dom/media/eme/MediaKeyStatusMap.h @@ -0,0 +1,78 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_dom_MediaKeyStatuses_h +#define mozilla_dom_MediaKeyStatuses_h + +#include "mozilla/ErrorResult.h" +#include "mozilla/Attributes.h" +#include "nsCycleCollectionParticipant.h" +#include "nsWrapperCache.h" + +#include "mozilla/dom/TypedArray.h" +#include "mozilla/dom/MediaKeyStatusMapBinding.h" +#include "mozilla/CDMCaps.h" + +class nsPIDOMWindow; +class ArrayBufferViewOrArrayBuffer; + +namespace mozilla { +namespace dom { + +class MediaKeyStatusMap MOZ_FINAL : public nsISupports, + public nsWrapperCache +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(MediaKeyStatusMap) + +public: + explicit MediaKeyStatusMap(nsPIDOMWindow* aParent); + +protected: + ~MediaKeyStatusMap(); + +public: + nsPIDOMWindow* GetParentObject() const; + + virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE; + + MediaKeyStatus Get(JSContext* aCx, + const ArrayBufferViewOrArrayBuffer& aKey, + ErrorResult& aRv) const; + + bool Has(JSContext* aCx, + const ArrayBufferViewOrArrayBuffer& aKey, + ErrorResult& aRv) const; + + void Keys(JSContext* aCx, + JS::MutableHandle aResult, + ErrorResult& aRv) const; + + void Values(JSContext* aCx, + JS::MutableHandle aResult, + ErrorResult& aRv) const; + + void Entries(JSContext* aCx, + JS::MutableHandle aResult, + ErrorResult& aRv) const; + + uint32_t GetSize(JSContext* aCx, ErrorResult& aRv) const; + + void Update(const nsTArray& keys); + +private: + nsresult UpdateInternal(const nsTArray& keys); + + nsCOMPtr mParent; + JS::Heap mMap; + nsresult mUpdateError; +}; + +} // namespace dom +} // namespace mozilla + +#endif diff --git a/dom/media/eme/moz.build b/dom/media/eme/moz.build index 6e3d3532be0..84fcad0b90f 100644 --- a/dom/media/eme/moz.build +++ b/dom/media/eme/moz.build @@ -10,6 +10,7 @@ EXPORTS.mozilla.dom += [ 'MediaKeyMessageEvent.h', 'MediaKeys.h', 'MediaKeySession.h', + 'MediaKeyStatusMap.h', 'MediaKeySystemAccess.h', ] @@ -30,6 +31,7 @@ UNIFIED_SOURCES += [ 'MediaKeyMessageEvent.cpp', 'MediaKeys.cpp', 'MediaKeySession.cpp', + 'MediaKeyStatusMap.cpp', 'MediaKeySystemAccess.cpp', ] diff --git a/dom/webidl/MediaKeySession.webidl b/dom/webidl/MediaKeySession.webidl index dade9b02b79..2cfc7a46c5a 100644 --- a/dom/webidl/MediaKeySession.webidl +++ b/dom/webidl/MediaKeySession.webidl @@ -23,22 +23,21 @@ interface MediaKeySession : EventTarget { readonly attribute Promise closed; + readonly attribute MediaKeyStatusMap keyStatuses; + [NewObject] - Promise generateRequest(DOMString initDataType, (ArrayBufferView or ArrayBuffer) initData); + Promise generateRequest(DOMString initDataType, BufferSource initData); [NewObject] Promise load(DOMString sessionId); // session operations [NewObject] - Promise update((ArrayBufferView or ArrayBuffer) response); + Promise update(BufferSource response); [NewObject] Promise close(); [NewObject] Promise remove(); - - [NewObject] - Promise> getUsableKeyIds(); }; diff --git a/dom/webidl/MediaKeyStatusMap.webidl b/dom/webidl/MediaKeyStatusMap.webidl new file mode 100644 index 00000000000..cf3ebc3c515 --- /dev/null +++ b/dom/webidl/MediaKeyStatusMap.webidl @@ -0,0 +1,36 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + * + * The origin of this IDL file is + * https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html + * + * Copyright © 2014 W3C® (MIT, ERCIM, Keio, Beihang), All Rights Reserved. + * W3C liability, trademark and document use rules apply. + */ + +enum MediaKeyStatus { + "usable", + "expired", + "output-downscaled", + "output-not-allowed", + "internal-error" +}; + +[Pref="media.eme.enabled"] +interface MediaKeyStatusMap { + [Throws] + readonly attribute unsigned long size; + + [Throws] + object keys(); + + [Throws] + object values(); + + [Throws] + object entries(); + + // XXX: forEach, @@iterator +}; diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build index ff8758687fe..b58314bebb8 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -807,6 +807,7 @@ if CONFIG['MOZ_EME']: 'MediaKeyMessageEvent.webidl', 'MediaKeys.webidl', 'MediaKeySession.webidl', + 'MediaKeyStatusMap.webidl', 'MediaKeySystemAccess.webidl', ] From 9a22e6ff6eeae719bbc9897d605994f91c37fa90 Mon Sep 17 00:00:00 2001 From: JW Wang Date: Sat, 31 Jan 2015 13:23:19 +1300 Subject: [PATCH 078/101] Bug 1121332. Part 5 - update EME mochitests for webidl changes. r=cpearce. --- .../test/test_eme_persistent_sessions.html | 30 ++++++++++--------- dom/media/test/test_eme_playback.html | 15 +++++----- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/dom/media/test/test_eme_persistent_sessions.html b/dom/media/test/test_eme_persistent_sessions.html index cdf6b38fa38..3069263cc06 100644 --- a/dom/media/test/test_eme_persistent_sessions.html +++ b/dom/media/test/test_eme_persistent_sessions.html @@ -31,14 +31,17 @@ function UsableKeyIdsMatch(usableKeyIds, expectedKeyIds) { function AwaitAllKeysUsable(session, keys, token) { return new Promise(function(resolve, reject) { function listener(event) { - session.getUsableKeyIds().then(function(usableKeyIds) { - var u = UsableKeyIdsMatch(usableKeyIds, keys); - if (UsableKeyIdsMatch(usableKeyIds, keys)) { - Log(token, "resolving AwaitAllKeysUsable promise"); - session.removeEventListener("keyschange", listener); - resolve(); - } - }, bail(token + " failed to get usableKeyIds")); + var map = session.keyStatuses; + var usableKeyIds = []; + for (var [key, val] of map.entries()) { + is(val, "usable", token + ": key status should be usable"); + usableKeyIds.push(key); + } + if (UsableKeyIdsMatch(usableKeyIds, keys)) { + Log(token, "resolving AwaitAllKeysUsable promise"); + session.removeEventListener("keyschange", listener); + resolve(); + } } session.addEventListener("keyschange", listener); }); @@ -47,12 +50,11 @@ function AwaitAllKeysUsable(session, keys, token) { function AwaitAllKeysNotUsable(session, token) { return new Promise(function(resolve, reject) { function listener(event) { - session.getUsableKeyIds().then(function(usableKeyIds) { - if (usableKeyIds.length == 0) { - session.removeEventListener("keyschange", listener); - resolve(); - } - }, bail(token + " failed to get usableKeyIds")); + var map = session.keyStatuses; + if (map.size == 0) { + session.removeEventListener("keyschange", listener); + resolve(); + } } session.addEventListener("keyschange", listener); }); diff --git a/dom/media/test/test_eme_playback.html b/dom/media/test/test_eme_playback.html index 9202a7cc4d3..addaba41e1b 100644 --- a/dom/media/test/test_eme_playback.html +++ b/dom/media/test/test_eme_playback.html @@ -22,13 +22,14 @@ function KeysChangeFunc(session, keys, token) { return function(ev) { var session = ev.target; session.gotKeysChanged = true; - session.getUsableKeyIds().then(function(keyIds) { - for (var k = 0; k < keyIds.length; k++) { - var kid = Base64ToHex(window.btoa(ArrayBufferToString(keyIds[k]))); - ok(kid in session.keyIdsReceived, TimeStamp(token) + " session.keyIdsReceived contained " + kid + " as expected."); - session.keyIdsReceived[kid] = true; - } - }, bail("Failed to get keyIds")); + + var map = session.keyStatuses; + for (var [key, val] of map.entries()) { + is(val, "usable", token + ": key status should be usable"); + var kid = Base64ToHex(window.btoa(ArrayBufferToString(key))); + ok(kid in session.keyIdsReceived, TimeStamp(token) + " session.keyIdsReceived contained " + kid + " as expected."); + session.keyIdsReceived[kid] = true; + } } } From 6304299d7a90504793253e1072556bcd7328066d Mon Sep 17 00:00:00 2001 From: JW Wang Date: Sat, 31 Jan 2015 13:23:59 +1300 Subject: [PATCH 079/101] Bug 1121332. Part 6 - update test_interfaces.html. r=bz --- dom/tests/mochitest/general/test_interfaces.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dom/tests/mochitest/general/test_interfaces.html b/dom/tests/mochitest/general/test_interfaces.html index 8c0ffb7fd48..b37bc44a01e 100644 --- a/dom/tests/mochitest/general/test_interfaces.html +++ b/dom/tests/mochitest/general/test_interfaces.html @@ -669,6 +669,8 @@ var interfaceNamesInGlobalScope = {name: "MediaKeySystemAccess", pref: "media.eme.enabled"}, // IMPORTANT: Do not change this list without review from a DOM peer! {name: "MediaKeyMessageEvent", pref: "media.eme.enabled"}, +// IMPORTANT: Do not change this list without review from a DOM peer! + {name: "MediaKeyStatusMap", pref: "media.eme.enabled"}, // IMPORTANT: Do not change this list without review from a DOM peer! "MediaList", // IMPORTANT: Do not change this list without review from a DOM peer! From 8ea3f3cab406a8a2abac6eab5ff7e45e2fc1171d Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 30 Jan 2015 16:32:26 -0800 Subject: [PATCH 080/101] Bug 1125236 - SpiderMonkey: Implement a minimal x86 disassembler r=jandem --- js/src/jit/Disassembler.cpp | 64 +++ js/src/jit/Disassembler.h | 254 +++++++++ js/src/jit/shared/Disassembler-x86-shared.cpp | 525 ++++++++++++++++++ js/src/moz.build | 2 + 4 files changed, 845 insertions(+) create mode 100644 js/src/jit/Disassembler.cpp create mode 100644 js/src/jit/Disassembler.h create mode 100644 js/src/jit/shared/Disassembler-x86-shared.cpp diff --git a/js/src/jit/Disassembler.cpp b/js/src/jit/Disassembler.cpp new file mode 100644 index 00000000000..433803e8293 --- /dev/null +++ b/js/src/jit/Disassembler.cpp @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "jit/Disassembler.h" + +using namespace js; +using namespace js::jit; +using namespace js::jit::Disassembler; + +#ifdef DEBUG +bool +Disassembler::ComplexAddress::operator==(const ComplexAddress &other) const +{ + return base_ == other.base_ && + index_ == other.index_ && + scale_ == other.scale_ && + disp_ == other.disp_ && + isPCRelative_ == other.isPCRelative_; +} + +bool +Disassembler::ComplexAddress::operator!=(const ComplexAddress &other) const +{ + return !operator==(other); +} + +bool +Disassembler::OtherOperand::operator==(const OtherOperand &other) const +{ + if (kind_ != other.kind_) + return false; + switch (kind_) { + case Imm: return u_.imm == other.u_.imm; + case GPR: return u_.gpr == other.u_.gpr; + case FPR: return u_.fpr == other.u_.fpr; + } + MOZ_CRASH("Unexpected OtherOperand kind"); +} + +bool +Disassembler::OtherOperand::operator!=(const OtherOperand &other) const +{ + return !operator==(other); +} + +bool +Disassembler::HeapAccess::operator==(const HeapAccess &other) const +{ + return kind_ == other.kind_ && + size_ == other.size_ && + address_ == other.address_ && + otherOperand_ == other.otherOperand_; +} + +bool +Disassembler::HeapAccess::operator!=(const HeapAccess &other) const +{ + return !operator==(other); +} + +#endif diff --git a/js/src/jit/Disassembler.h b/js/src/jit/Disassembler.h new file mode 100644 index 00000000000..df7ffa7c278 --- /dev/null +++ b/js/src/jit/Disassembler.h @@ -0,0 +1,254 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef jit_Disassembler_h +#define jit_Disassembler_h + +#include "jit/Registers.h" + +namespace js { +namespace jit { + +namespace Disassembler { + +class ComplexAddress { + int32_t disp_; + Register::Code base_ : 8; + Register::Code index_ : 8; + int8_t scale_; // log2 encoding + bool isPCRelative_; + + public: + ComplexAddress() + : disp_(0), + base_(Registers::Invalid), + index_(Registers::Invalid), + scale_(0), + isPCRelative_(false) + { + MOZ_ASSERT(*this == *this); + } + + ComplexAddress(int32_t disp, Register::Code base) + : disp_(disp), + base_(base), + index_(Registers::Invalid), + scale_(0), + isPCRelative_(false) + { + MOZ_ASSERT(*this == *this); + MOZ_ASSERT(base != Registers::Invalid); + MOZ_ASSERT(base_ == base); + } + + ComplexAddress(int32_t disp, Register::Code base, Register::Code index, int scale) + : disp_(disp), + base_(base), + index_(index), + scale_(scale), + isPCRelative_(false) + { + MOZ_ASSERT(scale >= 0 && scale < 4); + MOZ_ASSERT_IF(index == Registers::Invalid, scale == 0); + MOZ_ASSERT(*this == *this); + MOZ_ASSERT(base_ == base); + MOZ_ASSERT(index_ == index); + } + + explicit ComplexAddress(const void *addr) + : disp_(static_cast(reinterpret_cast(addr))), + base_(Registers::Invalid), + index_(Registers::Invalid), + scale_(0), + isPCRelative_(false) + { + MOZ_ASSERT(*this == *this); + MOZ_ASSERT(reinterpret_cast(uintptr_t(disp_)) == addr); + } + + explicit ComplexAddress(const Operand &op) { +#if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86) + switch (op.kind()) { + case Operand::MEM_REG_DISP: + *this = ComplexAddress(op.disp(), op.base()); + return; + case Operand::MEM_SCALE: + *this = ComplexAddress(op.disp(), op.base(), op.index(), op.scale()); + return; + default: + break; + } +#endif + MOZ_CRASH("Unexpected Operand kind"); + } + + bool isPCRelative() const { + return isPCRelative_; + } + + int32_t disp() const { + return disp_; + } + + Register::Code base() const { + return base_; + } + + Register::Code index() const { + return index_; + } + + uint32_t scale() const { + return scale_; + } + +#ifdef DEBUG + bool operator==(const ComplexAddress &other) const; + bool operator!=(const ComplexAddress &other) const; +#endif +}; + +// An operand other than a memory operand -- a register or an immediate. +class OtherOperand { + public: + enum Kind { + Imm, + GPR, + FPR, + }; + + private: + Kind kind_; + union { + int32_t imm; + Register::Code gpr; + FloatRegister::Code fpr; + } u_; + + public: + OtherOperand() + : kind_(Imm) + { + u_.imm = 0; + MOZ_ASSERT(*this == *this); + } + + explicit OtherOperand(int32_t imm) + : kind_(Imm) + { + u_.imm = imm; + MOZ_ASSERT(*this == *this); + } + + explicit OtherOperand(Register::Code gpr) + : kind_(GPR) + { + u_.gpr = gpr; + MOZ_ASSERT(*this == *this); + } + + explicit OtherOperand(FloatRegister::Code fpr) + : kind_(FPR) + { + u_.fpr = fpr; + MOZ_ASSERT(*this == *this); + } + + Kind kind() const { + return kind_; + } + + int32_t imm() const { + MOZ_ASSERT(kind_ == Imm); + return u_.imm; + } + + Register::Code gpr() const { + MOZ_ASSERT(kind_ == GPR); + return u_.gpr; + } + + FloatRegister::Code fpr() const { + MOZ_ASSERT(kind_ == FPR); + return u_.fpr; + } + +#ifdef DEBUG + bool operator==(const OtherOperand &other) const; + bool operator!=(const OtherOperand &other) const; +#endif +}; + +class HeapAccess { + public: + enum Kind { + Unknown, + Load, // any bits not covered by the load are zeroed + LoadSext32, // like Load, but sign-extend to 32 bits + Store + }; + + private: + Kind kind_; + size_t size_; // The number of bytes of memory accessed + ComplexAddress address_; + OtherOperand otherOperand_; + + public: + HeapAccess() + : kind_(Unknown), + size_(0) + { + MOZ_ASSERT(*this == *this); + } + + HeapAccess(Kind kind, size_t size, const ComplexAddress &address, const OtherOperand &otherOperand) + : kind_(kind), + size_(size), + address_(address), + otherOperand_(otherOperand) + { + MOZ_ASSERT(kind != Unknown); + MOZ_ASSERT_IF(kind == LoadSext32, otherOperand.kind() != OtherOperand::FPR); + MOZ_ASSERT_IF(kind == Load || kind == LoadSext32, otherOperand.kind() != OtherOperand::Imm); + MOZ_ASSERT(*this == *this); + } + + Kind kind() const { + return kind_; + } + + size_t size() const { + MOZ_ASSERT(kind_ != Unknown); + return size_; + } + + const ComplexAddress &address() const { + return address_; + } + + const OtherOperand &otherOperand() const { + return otherOperand_; + } + +#ifdef DEBUG + bool operator==(const HeapAccess &other) const; + bool operator!=(const HeapAccess &other) const; +#endif +}; + +MOZ_COLD uint8_t *DisassembleHeapAccess(uint8_t *ptr, HeapAccess *access); + +#ifdef DEBUG +void DumpHeapAccess(const HeapAccess &access); +#endif + +} // namespace Disassembler + +} // namespace jit +} // namespace js + +#endif /* jit_Disassembler_h */ diff --git a/js/src/jit/shared/Disassembler-x86-shared.cpp b/js/src/jit/shared/Disassembler-x86-shared.cpp new file mode 100644 index 00000000000..53497ec2b97 --- /dev/null +++ b/js/src/jit/shared/Disassembler-x86-shared.cpp @@ -0,0 +1,525 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "jit/Disassembler.h" + +#include "jit/shared/Encoding-x86-shared.h" + +using namespace js; +using namespace js::jit; +using namespace js::jit::X86Encoding; +using namespace js::jit::Disassembler; + +static bool REX_W(uint8_t rex) { return (rex >> 3) & 0x1; } +static bool REX_R(uint8_t rex) { return (rex >> 2) & 0x1; } +static bool REX_X(uint8_t rex) { return (rex >> 1) & 0x1; } +static bool REX_B(uint8_t rex) { return (rex >> 0) & 0x1; } + +static uint8_t +MakeREXFlags(bool w, bool r, bool x, bool b) +{ + uint8_t rex = (w << 3) | (r << 2) | (x << 1) | (b << 0); + MOZ_ASSERT(REX_W(rex) == w); + MOZ_ASSERT(REX_R(rex) == r); + MOZ_ASSERT(REX_X(rex) == x); + MOZ_ASSERT(REX_B(rex) == b); + return rex; +} + +static ModRmMode +ModRM_Mode(uint8_t modrm) +{ + return ModRmMode((modrm >> 6) & 0x3); +} + +static uint8_t +ModRM_Reg(uint8_t modrm) +{ + return (modrm >> 3) & 0x7; +} + +static uint8_t +ModRM_RM(uint8_t modrm) +{ + return (modrm >> 0) & 0x7; +} + +static bool +ModRM_hasSIB(uint8_t modrm) +{ + return ModRM_Mode(modrm) != ModRmRegister && ModRM_RM(modrm) == hasSib; +} +static bool +ModRM_hasDisp8(uint8_t modrm) +{ + return ModRM_Mode(modrm) == ModRmMemoryDisp8; +} +static bool +ModRM_hasRIP(uint8_t modrm) +{ +#ifdef JS_CODEGEN_X64 + return ModRM_Mode(modrm) == ModRmMemoryNoDisp && ModRM_RM(modrm) == noBase; +#else + return false; +#endif +} +static bool +ModRM_hasDisp32(uint8_t modrm) +{ + return ModRM_Mode(modrm) == ModRmMemoryDisp32 || + ModRM_hasRIP(modrm); +} + +static uint8_t +SIB_SS(uint8_t sib) +{ + return (sib >> 6) & 0x3; +} + +static uint8_t +SIB_Index(uint8_t sib) +{ + return (sib >> 3) & 0x7; +} + +static uint8_t +SIB_Base(uint8_t sib) +{ + return (sib >> 0) & 0x7; +} + +static bool +SIB_hasRIP(uint8_t sib) +{ + return SIB_Base(sib) == noBase && SIB_Index(sib) == noIndex; +} + +static bool +HasRIP(uint8_t modrm, uint8_t sib, uint8_t rex) +{ + return ModRM_hasRIP(modrm) && SIB_hasRIP(sib); +} + +static bool +HasDisp8(uint8_t modrm) +{ + return ModRM_hasDisp8(modrm); +} + +static bool +HasDisp32(uint8_t modrm, uint8_t sib) +{ + return ModRM_hasDisp32(modrm) || + (SIB_Base(sib) == noBase && + SIB_Index(sib) == noIndex && + ModRM_Mode(modrm) == ModRmMemoryNoDisp); +} + +static uint32_t +Reg(uint8_t modrm, uint8_t sib, uint8_t rex) +{ + return ModRM_Reg(modrm) | (REX_R(rex) << 3); +} + +static bool +HasBase(uint8_t modrm, uint8_t sib) +{ + return !ModRM_hasSIB(modrm) || + SIB_Base(sib) != noBase || + SIB_Index(sib) != noIndex || + ModRM_Mode(modrm) != ModRmMemoryNoDisp; +} + +static RegisterID +DecodeBase(uint8_t modrm, uint8_t sib, uint8_t rex) +{ + return HasBase(modrm, sib) + ? RegisterID((ModRM_hasSIB(modrm) ? SIB_Base(sib) : ModRM_RM(modrm)) | (REX_B(rex) << 3)) + : invalid_reg; +} + +static RegisterID +DecodeIndex(uint8_t modrm, uint8_t sib, uint8_t rex) +{ + RegisterID index = RegisterID(SIB_Index(sib) | (REX_X(rex) << 3)); + return ModRM_hasSIB(modrm) && index != noIndex ? index : invalid_reg; +} + +static uint32_t +DecodeScale(uint8_t modrm, uint8_t sib, uint8_t rex) +{ + return ModRM_hasSIB(modrm) ? SIB_SS(sib) : 0; +} + +#define PackOpcode(op0, op1, op2) ((op0) | ((op1) << 8) | ((op2) << 16)) +#define Pack2ByteOpcode(op1) PackOpcode(OP_2BYTE_ESCAPE, op1, 0) +#define Pack3ByteOpcode(op1, op2) PackOpcode(OP_2BYTE_ESCAPE, op1, op2) + +uint8_t * +js::jit::Disassembler::DisassembleHeapAccess(uint8_t *ptr, HeapAccess *access) +{ + VexOperandType type = VEX_PS; + uint32_t opcode = OP_HLT; + uint8_t modrm = 0; + uint8_t sib = 0; + uint8_t rex = 0; + int32_t disp = 0; + int32_t imm = 0; + bool haveImm = false; + int opsize = 4; + + // Legacy prefixes + switch (*ptr) { + case PRE_LOCK: + case PRE_PREDICT_BRANCH_NOT_TAKEN: // (obsolete), aka %cs + case 0x3E: // aka predict-branch-taken (obsolete) + case 0x36: // %ss + case 0x26: // %es + case 0x64: // %fs + case 0x65: // %gs + case 0x67: // address-size override + MOZ_CRASH("Unable to disassemble instruction"); + case PRE_SSE_F2: // aka REPNZ/REPNE + type = VEX_SD; + ptr++; + break; + case PRE_SSE_F3: // aka REP/REPE/REPZ + type = VEX_SS; + ptr++; + break; + case PRE_SSE_66: // aka PRE_OPERAND_SIZE + type = VEX_PD; + opsize = 2; + ptr++; + break; + default: + break; + } + + // REX and VEX prefixes + { + int x = 0, b = 0, m = 1, w = 0; + int r, l, p; + switch (*ptr) { +#ifdef JS_CODEGEN_X64 + case PRE_REX | 0x0: case PRE_REX | 0x1: case PRE_REX | 0x2: case PRE_REX | 0x3: + case PRE_REX | 0x4: case PRE_REX | 0x5: case PRE_REX | 0x6: case PRE_REX | 0x7: + case PRE_REX | 0x8: case PRE_REX | 0x9: case PRE_REX | 0xa: case PRE_REX | 0xb: + case PRE_REX | 0xc: case PRE_REX | 0xd: case PRE_REX | 0xe: case PRE_REX | 0xf: + rex = *ptr++ & 0xf; + goto rex_done; +#endif + case PRE_VEX_C4: { + if (type != VEX_PS) + MOZ_CRASH("Unable to disassemble instruction"); + ++ptr; + uint8_t c4a = *ptr++ ^ 0xe0; + uint8_t c4b = *ptr++ ^ 0x78; + r = (c4a >> 7) & 0x1; + x = (c4a >> 6) & 0x1; + b = (c4a >> 5) & 0x1; + m = (c4a >> 0) & 0x1f; + w = (c4b >> 7) & 0x1; + l = (c4b >> 2) & 0x1; + p = (c4b >> 0) & 0x3; + break; + } + case PRE_VEX_C5: { + if (type != VEX_PS) + MOZ_CRASH("Unable to disassemble instruction"); + ++ptr; + uint8_t c5 = *ptr++ ^ 0xf8; + r = (c5 >> 7) & 0x1; + l = (c5 >> 2) & 0x1; + p = (c5 >> 0) & 0x3; + break; + } + default: + goto rex_done; + } + type = VexOperandType(p); + rex = MakeREXFlags(w, r, x, b); + switch (m) { + case 0x1: + opcode = Pack2ByteOpcode(*ptr++); + goto opcode_done; + case 0x2: + opcode = Pack3ByteOpcode(ESCAPE_38, *ptr++); + goto opcode_done; + case 0x3: + opcode = Pack3ByteOpcode(ESCAPE_3A, *ptr++); + goto opcode_done; + default: + MOZ_CRASH("Unable to disassemble instruction"); + } + if (l != 0) // 256-bit SIMD + MOZ_CRASH("Unable to disassemble instruction"); + } + rex_done:; + if (REX_W(rex)) + opsize = 8; + + // Opcode. + opcode = *ptr++; + switch (opcode) { +#ifdef JS_CODEGEN_X64 + case OP_PUSH_EAX + 0: case OP_PUSH_EAX + 1: case OP_PUSH_EAX + 2: case OP_PUSH_EAX + 3: + case OP_PUSH_EAX + 4: case OP_PUSH_EAX + 5: case OP_PUSH_EAX + 6: case OP_PUSH_EAX + 7: + case OP_POP_EAX + 0: case OP_POP_EAX + 1: case OP_POP_EAX + 2: case OP_POP_EAX + 3: + case OP_POP_EAX + 4: case OP_POP_EAX + 5: case OP_POP_EAX + 6: case OP_POP_EAX + 7: + case OP_PUSH_Iz: + case OP_PUSH_Ib: + opsize = 8; + break; +#endif + case OP_2BYTE_ESCAPE: + opcode |= *ptr << 8; + switch (*ptr++) { + case ESCAPE_38: + case ESCAPE_3A: + opcode |= *ptr++ << 16; + break; + default: + break; + } + break; + default: + break; + } + opcode_done:; + + // ModR/M + modrm = *ptr++; + + // SIB + if (ModRM_hasSIB(modrm)) + sib = *ptr++; + + // Address Displacement + if (HasDisp8(modrm)) { + disp = int8_t(*ptr++); + } else if (HasDisp32(modrm, sib)) { + memcpy(&disp, ptr, sizeof(int32_t)); + ptr += sizeof(int32_t); + } + + // Immediate operand + switch (opcode) { + case OP_PUSH_Ib: + case OP_IMUL_GvEvIb: + case OP_GROUP1_EbIb: + case OP_GROUP1_EvIb: + case OP_TEST_EAXIb: + case OP_GROUP2_EvIb: + case OP_GROUP11_EvIb: + case OP_GROUP3_EbIb: + case Pack2ByteOpcode(OP2_PSHUFD_VdqWdqIb): + case Pack2ByteOpcode(OP2_PSLLD_UdqIb): // aka OP2_PSRAD_UdqIb, aka OP2_PSRLD_UdqIb + case Pack2ByteOpcode(OP2_PEXTRW_GdUdIb): + case Pack2ByteOpcode(OP2_SHUFPS_VpsWpsIb): + case Pack3ByteOpcode(ESCAPE_3A, OP3_PEXTRD_EdVdqIb): + case Pack3ByteOpcode(ESCAPE_3A, OP3_BLENDPS_VpsWpsIb): + case Pack3ByteOpcode(ESCAPE_3A, OP3_PINSRD_VdqEdIb): + // 8-bit signed immediate + imm = int8_t(*ptr++); + haveImm = true; + break; + case OP_RET_Iz: + // 16-bit unsigned immediate + memcpy(&imm, ptr, sizeof(int16_t)); + ptr += sizeof(int16_t); + haveImm = true; + break; + case OP_ADD_EAXIv: + case OP_OR_EAXIv: + case OP_AND_EAXIv: + case OP_SUB_EAXIv: + case OP_XOR_EAXIv: + case OP_CMP_EAXIv: + case OP_PUSH_Iz: + case OP_IMUL_GvEvIz: + case OP_GROUP1_EvIz: + case OP_TEST_EAXIv: + case OP_MOV_EAXIv: + case OP_GROUP3_EvIz: + // 32-bit signed immediate + memcpy(&imm, ptr, sizeof(int32_t)); + ptr += sizeof(int32_t); + haveImm = true; + break; + case OP_GROUP11_EvIz: + // opsize-sized signed immediate + memcpy(&imm, ptr, opsize); + imm = (imm << (32 - opsize * 8)) >> (32 - opsize * 8); + ptr += opsize; + haveImm = true; + break; + default: + break; + } + + // Interpret the opcode. + if (HasRIP(modrm, sib, rex)) + MOZ_CRASH("Unable to disassemble instruction"); + size_t memSize = 0; + OtherOperand otherOperand(imm); + HeapAccess::Kind kind = HeapAccess::Unknown; + RegisterID gpr(RegisterID(Reg(modrm, sib, rex))); + XMMRegisterID xmm(XMMRegisterID(Reg(modrm, sib, rex))); + ComplexAddress addr(disp, + DecodeBase(modrm, sib, rex), + DecodeIndex(modrm, sib, rex), + DecodeScale(modrm, sib, rex)); + switch (opcode) { + case OP_GROUP11_EvIb: + if (gpr != RegisterID(GROUP11_MOV)) + MOZ_CRASH("Unable to disassemble instruction"); + MOZ_ASSERT(haveImm); + memSize = 1; + kind = HeapAccess::Store; + break; + case OP_GROUP11_EvIz: + if (gpr != RegisterID(GROUP11_MOV)) + MOZ_CRASH("Unable to disassemble instruction"); + MOZ_ASSERT(haveImm); + memSize = opsize; + kind = HeapAccess::Store; + break; + case OP_MOV_GvEv: + MOZ_ASSERT(!haveImm); + otherOperand = OtherOperand(gpr); + memSize = opsize; + kind = HeapAccess::Load; + break; + case OP_MOV_GvEb: + MOZ_ASSERT(!haveImm); + otherOperand = OtherOperand(gpr); + memSize = 1; + kind = HeapAccess::Load; + break; + case OP_MOV_EvGv: + if (!haveImm) + otherOperand = OtherOperand(gpr); + memSize = opsize; + kind = HeapAccess::Store; + break; + case OP_MOV_EbGv: + if (!haveImm) + otherOperand = OtherOperand(gpr); + memSize = 1; + kind = HeapAccess::Store; + break; + case Pack2ByteOpcode(OP2_MOVZX_GvEb): + MOZ_ASSERT(!haveImm); + otherOperand = OtherOperand(gpr); + memSize = 1; + kind = HeapAccess::Load; + break; + case Pack2ByteOpcode(OP2_MOVZX_GvEw): + MOZ_ASSERT(!haveImm); + otherOperand = OtherOperand(gpr); + memSize = 2; + kind = HeapAccess::Load; + break; + case Pack2ByteOpcode(OP2_MOVSX_GvEb): + MOZ_ASSERT(!haveImm); + otherOperand = OtherOperand(gpr); + memSize = 1; + kind = HeapAccess::LoadSext32; + break; + case Pack2ByteOpcode(OP2_MOVSX_GvEw): + MOZ_ASSERT(!haveImm); + otherOperand = OtherOperand(gpr); + memSize = 2; + kind = HeapAccess::LoadSext32; + break; + case Pack2ByteOpcode(OP2_MOVDQ_VdqWdq): // aka OP2_MOVDQ_VsdWsd + case Pack2ByteOpcode(OP2_MOVAPS_VsdWsd): + MOZ_ASSERT(!haveImm); + otherOperand = OtherOperand(xmm); + memSize = 16; + kind = HeapAccess::Load; + break; + case Pack2ByteOpcode(OP2_MOVSD_VsdWsd): // aka OP2_MOVPS_VpsWps + MOZ_ASSERT(!haveImm); + otherOperand = OtherOperand(xmm); + switch (type) { + case VEX_SS: memSize = 4; break; + case VEX_SD: memSize = 8; break; + case VEX_PS: + case VEX_PD: memSize = 16; break; + default: MOZ_CRASH("Unexpected VEX type"); + } + kind = HeapAccess::Load; + break; + case Pack2ByteOpcode(OP2_MOVDQ_WdqVdq): + MOZ_ASSERT(!haveImm); + otherOperand = OtherOperand(xmm); + memSize = 16; + kind = HeapAccess::Store; + break; + case Pack2ByteOpcode(OP2_MOVSD_WsdVsd): // aka OP2_MOVPS_WpsVps + MOZ_ASSERT(!haveImm); + otherOperand = OtherOperand(xmm); + switch (type) { + case VEX_SS: memSize = 4; break; + case VEX_SD: memSize = 8; break; + case VEX_PS: + case VEX_PD: memSize = 16; break; + default: MOZ_CRASH("Unexpected VEX type"); + } + kind = HeapAccess::Store; + break; + default: + MOZ_CRASH("Unable to disassemble instruction"); + } + + *access = HeapAccess(kind, memSize, addr, otherOperand); + return ptr; +} + +#ifdef DEBUG +void +js::jit::Disassembler::DumpHeapAccess(const HeapAccess &access) +{ + switch (access.kind()) { + case HeapAccess::Store: fprintf(stderr, "store"); break; + case HeapAccess::Load: fprintf(stderr, "load"); break; + case HeapAccess::LoadSext32: fprintf(stderr, "loadSext32"); break; + default: fprintf(stderr, "unknown"); break; + } + fprintf(stderr, "%u ", unsigned(access.size())); + + switch (access.otherOperand().kind()) { + case OtherOperand::Imm: fprintf(stderr, "imm %d", access.otherOperand().imm()); break; + case OtherOperand::GPR: fprintf(stderr, "gpr %s", X86Encoding::GPRegName(access.otherOperand().gpr())); break; + case OtherOperand::FPR: fprintf(stderr, "fpr %s", X86Encoding::XMMRegName(access.otherOperand().fpr())); break; + default: fprintf(stderr, "unknown"); + } + + fprintf(stderr, " @ "); + + if (access.address().isPCRelative()) { + fprintf(stderr, MEM_o32r " ", ADDR_o32r(access.address().disp())); + } else if (access.address().index() != X86Encoding::invalid_reg) { + if (access.address().base() != X86Encoding::invalid_reg) { + fprintf(stderr, MEM_obs " ", + ADDR_obs(access.address().disp(), access.address().base(), + access.address().index(), access.address().scale())); + } else { + fprintf(stderr, MEM_os " ", + ADDR_os(access.address().disp(), + access.address().index(), access.address().scale())); + } + } else if (access.address().base() != X86Encoding::invalid_reg) { + fprintf(stderr, MEM_ob " ", ADDR_ob(access.address().disp(), access.address().base())); + } else { + fprintf(stderr, MEM_o " ", ADDR_o(access.address().disp())); + } + + fprintf(stderr, "\n"); +} +#endif diff --git a/js/src/moz.build b/js/src/moz.build index e93d2eff71a..0e6a6496220 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -154,6 +154,7 @@ UNIFIED_SOURCES += [ 'jit/C1Spewer.cpp', 'jit/CodeGenerator.cpp', 'jit/CompileWrappers.cpp', + 'jit/Disassembler.cpp', 'jit/EdgeCaseAnalysis.cpp', 'jit/EffectiveAddressAnalysis.cpp', 'jit/ExecutableAllocator.cpp', @@ -326,6 +327,7 @@ elif CONFIG['JS_CODEGEN_X86'] or CONFIG['JS_CODEGEN_X64']: 'jit/shared/BaselineCompiler-x86-shared.cpp', 'jit/shared/BaselineIC-x86-shared.cpp', 'jit/shared/CodeGenerator-x86-shared.cpp', + 'jit/shared/Disassembler-x86-shared.cpp', 'jit/shared/Lowering-x86-shared.cpp', 'jit/shared/MacroAssembler-x86-shared.cpp', 'jit/shared/MoveEmitter-x86-shared.cpp', From 7f2be25136913c45780904bae0e7f51728aff2d0 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Fri, 30 Jan 2015 16:32:26 -0800 Subject: [PATCH 081/101] Bug 1125236 - SpiderMonkey: Verify that asm.js heap loads and stores can be correctly disassembled r=jandem --- js/src/jit/Disassembler.h | 10 ++++ js/src/jit/arm/Assembler-arm.h | 6 +++ js/src/jit/mips/Assembler-mips.h | 6 +++ js/src/jit/shared/Assembler-shared.h | 4 ++ js/src/jit/shared/Assembler-x86-shared.cpp | 10 ++++ js/src/jit/shared/Assembler-x86-shared.h | 3 ++ js/src/jit/shared/CodeGenerator-shared-inl.h | 52 ++++++++++++++++++++ js/src/jit/shared/CodeGenerator-shared.h | 4 ++ js/src/jit/x64/CodeGenerator-x64.cpp | 2 + 9 files changed, 97 insertions(+) diff --git a/js/src/jit/Disassembler.h b/js/src/jit/Disassembler.h index df7ffa7c278..63e9d5f793f 100644 --- a/js/src/jit/Disassembler.h +++ b/js/src/jit/Disassembler.h @@ -244,6 +244,16 @@ MOZ_COLD uint8_t *DisassembleHeapAccess(uint8_t *ptr, HeapAccess *access); #ifdef DEBUG void DumpHeapAccess(const HeapAccess &access); + +inline void +VerifyHeapAccess(uint8_t *begin, uint8_t *end, const HeapAccess &expected) +{ + HeapAccess disassembled; + uint8_t *e = DisassembleHeapAccess(begin, &disassembled); + MOZ_ASSERT(e == end); + MOZ_ASSERT(disassembled == expected); +} + #endif } // namespace Disassembler diff --git a/js/src/jit/arm/Assembler-arm.h b/js/src/jit/arm/Assembler-arm.h index 1ee28b2db95..1ca6bf71cfe 100644 --- a/js/src/jit/arm/Assembler-arm.h +++ b/js/src/jit/arm/Assembler-arm.h @@ -1799,6 +1799,12 @@ class Assembler : public AssemblerShared bool bailed() { return m_buffer.bail(); } + + void verifyHeapAccessDisassembly(uint32_t begin, uint32_t end, + const Disassembler::HeapAccess &heapAccess) + { + // Implement this if we implement a disassembler. + } }; // Assembler // An Instruction is a structure for both encoding and decoding any and all ARM diff --git a/js/src/jit/mips/Assembler-mips.h b/js/src/jit/mips/Assembler-mips.h index 906270c118a..8d5410a04cd 100644 --- a/js/src/jit/mips/Assembler-mips.h +++ b/js/src/jit/mips/Assembler-mips.h @@ -1086,6 +1086,12 @@ class Assembler : public AssemblerShared bool bailed() { return m_buffer.bail(); } + + void verifyHeapAccessDisassembly(uint32_t begin, uint32_t end, + const Disassembler::HeapAccess &heapAccess) + { + // Implement this if we implement a disassembler. + } }; // Assembler // sll zero, zero, 0 diff --git a/js/src/jit/shared/Assembler-shared.h b/js/src/jit/shared/Assembler-shared.h index 6eb1c36c8e2..c7fa7d1e215 100644 --- a/js/src/jit/shared/Assembler-shared.h +++ b/js/src/jit/shared/Assembler-shared.h @@ -30,6 +30,10 @@ namespace js { namespace jit { +namespace Disassembler { +class HeapAccess; +}; + static const uint32_t Simd128DataSize = 4 * sizeof(int32_t); static_assert(Simd128DataSize == 4 * sizeof(int32_t), "SIMD data should be able to contain int32x4"); static_assert(Simd128DataSize == 4 * sizeof(float), "SIMD data should be able to contain float32x4"); diff --git a/js/src/jit/shared/Assembler-x86-shared.cpp b/js/src/jit/shared/Assembler-x86-shared.cpp index 71c6ca5cd05..a4f95440d12 100644 --- a/js/src/jit/shared/Assembler-x86-shared.cpp +++ b/js/src/jit/shared/Assembler-x86-shared.cpp @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "gc/Marking.h" +#include "jit/Disassembler.h" #include "jit/JitCompartment.h" #if defined(JS_CODEGEN_X86) # include "jit/x86/MacroAssembler-x86.h" @@ -139,6 +140,15 @@ AssemblerX86Shared::InvertCondition(Condition cond) } } +void +AssemblerX86Shared::verifyHeapAccessDisassembly(uint32_t begin, uint32_t end, + const Disassembler::HeapAccess &heapAccess) +{ +#ifdef DEBUG + Disassembler::VerifyHeapAccess(masm.data() + begin, masm.data() + end, heapAccess); +#endif +} + CPUInfo::SSEVersion CPUInfo::maxSSEVersion = UnknownSSE; CPUInfo::SSEVersion CPUInfo::maxEnabledSSEVersion = UnknownSSE; bool CPUInfo::avxPresent = false; diff --git a/js/src/jit/shared/Assembler-x86-shared.h b/js/src/jit/shared/Assembler-x86-shared.h index b150b5e800b..34934dab8cb 100644 --- a/js/src/jit/shared/Assembler-x86-shared.h +++ b/js/src/jit/shared/Assembler-x86-shared.h @@ -2594,6 +2594,9 @@ class AssemblerX86Shared : public AssemblerShared *ptr == 0xE8); // CALL *ptr = enabled ? 0xE8 : 0x3D; } + + MOZ_COLD void verifyHeapAccessDisassembly(uint32_t begin, uint32_t end, + const Disassembler::HeapAccess &heapAccess); }; } // namespace jit diff --git a/js/src/jit/shared/CodeGenerator-shared-inl.h b/js/src/jit/shared/CodeGenerator-shared-inl.h index 540e3e25a6e..4025ad6b2fd 100644 --- a/js/src/jit/shared/CodeGenerator-shared-inl.h +++ b/js/src/jit/shared/CodeGenerator-shared-inl.h @@ -8,6 +8,7 @@ #define jit_shared_CodeGenerator_shared_inl_h #include "jit/shared/CodeGenerator-shared.h" +#include "jit/Disassembler.h" namespace js { namespace jit { @@ -183,6 +184,57 @@ CodeGeneratorShared::restoreLiveVolatile(LInstruction *ins) masm.PopRegsInMask(regs); } +void +CodeGeneratorShared::verifyHeapAccessDisassembly(uint32_t begin, uint32_t end, bool isLoad, + Scalar::Type type, const Operand &mem, + LAllocation alloc) +{ +#ifdef DEBUG + using namespace Disassembler; + + OtherOperand op; + Disassembler::HeapAccess::Kind kind = isLoad ? HeapAccess::Load : HeapAccess::Store; + switch (type) { + case Scalar::Int8: + case Scalar::Int16: + if (kind == HeapAccess::Load) + kind = HeapAccess::LoadSext32; + // FALL THROUGH + case Scalar::Uint8: + case Scalar::Uint16: + case Scalar::Int32: + case Scalar::Uint32: + if (!alloc.isConstant()) { + op = OtherOperand(ToRegister(alloc).code()); + } else { + int32_t i = ToInt32(&alloc); + + // Sign-extend the immediate value out to 32 bits. We do this even + // for unsigned element types so that we match what the disassembly + // code does, as it doesn't know about signedness of stores. + unsigned shift = 32 - TypedArrayElemSize(type) * 8; + i = i << shift >> shift; + + op = OtherOperand(i); + } + break; + case Scalar::Float32: + case Scalar::Float64: + case Scalar::Float32x4: + case Scalar::Int32x4: + op = OtherOperand(ToFloatRegister(alloc).code()); + break; + case Scalar::Uint8Clamped: + case Scalar::MaxTypedArrayViewType: + MOZ_CRASH("Unexpected array type"); + } + + masm.verifyHeapAccessDisassembly(begin, end, + HeapAccess(kind, TypedArrayElemSize(type), + ComplexAddress(mem), op)); +#endif +} + } // ion } // js diff --git a/js/src/jit/shared/CodeGenerator-shared.h b/js/src/jit/shared/CodeGenerator-shared.h index 9bc64d7371b..12d2354de56 100644 --- a/js/src/jit/shared/CodeGenerator-shared.h +++ b/js/src/jit/shared/CodeGenerator-shared.h @@ -545,6 +545,10 @@ class CodeGeneratorShared : public LElementVisitor emitTracelogScriptStop(); #endif } + + inline void verifyHeapAccessDisassembly(uint32_t begin, uint32_t end, bool isLoad, + Scalar::Type type, const Operand &mem, + LAllocation alloc); }; // An out-of-line path is generated at the end of the function. diff --git a/js/src/jit/x64/CodeGenerator-x64.cpp b/js/src/jit/x64/CodeGenerator-x64.cpp index 76666e49226..96d4b8a4bb0 100644 --- a/js/src/jit/x64/CodeGenerator-x64.cpp +++ b/js/src/jit/x64/CodeGenerator-x64.cpp @@ -300,6 +300,7 @@ CodeGeneratorX64::visitAsmJSLoadHeap(LAsmJSLoadHeap *ins) MOZ_CRASH("unexpected array type"); } uint32_t after = masm.size(); + verifyHeapAccessDisassembly(before, after, /*isLoad=*/true, vt, srcAddr, *out->output()); if (ool) masm.bind(ool->rejoin()); memoryBarrier(ins->mir()->barrierAfter()); @@ -366,6 +367,7 @@ CodeGeneratorX64::visitAsmJSStoreHeap(LAsmJSStoreHeap *ins) } } uint32_t after = masm.size(); + verifyHeapAccessDisassembly(before, after, /*isLoad=*/false, vt, dstAddr, *ins->value()); if (rejoin.used()) masm.bind(&rejoin); memoryBarrier(ins->mir()->barrierAfter()); From 638211b0fa771d19a0897364abbf223ca50a95d0 Mon Sep 17 00:00:00 2001 From: Jim Blandy Date: Mon, 26 Jan 2015 11:48:35 -0600 Subject: [PATCH 082/101] Bug 1095145: Remove compileAndGoGlobal argument to Debugger::onNewScript, and simplify accordingly. r=shu --- js/src/builtin/Eval.cpp | 3 +- js/src/frontend/BytecodeEmitter.cpp | 5 +-- js/src/jsapi.cpp | 3 +- js/src/jsscript.cpp | 9 ++---- js/src/vm/Debugger.cpp | 50 +++++++++-------------------- js/src/vm/Debugger.h | 12 +++---- js/src/vm/HelperThreads.cpp | 5 +-- 7 files changed, 27 insertions(+), 60 deletions(-) diff --git a/js/src/builtin/Eval.cpp b/js/src/builtin/Eval.cpp index fc0a34dd827..d2fc5aff779 100644 --- a/js/src/builtin/Eval.cpp +++ b/js/src/builtin/Eval.cpp @@ -509,8 +509,7 @@ js::ExecuteInGlobalAndReturnScope(JSContext *cx, HandleObject global, HandleScri if (!script) return false; - Rooted global(cx, script->compileAndGo() ? &script->global() : nullptr); - Debugger::onNewScript(cx, script, global); + Debugger::onNewScript(cx, script); } RootedObject scope(cx, JS_NewPlainObject(cx)); diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index f9afa29c22e..ee0cc5b4261 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -2197,10 +2197,7 @@ BytecodeEmitter::tellDebuggerAboutCompiledScript(ExclusiveContext *cx) // Lazy scripts are never top level (despite always being invoked with a // nullptr parent), and so the hook should never be fired. if (emitterMode != LazyFunction && !parent) { - GlobalObject *compileAndGoGlobal = nullptr; - if (script->compileAndGo()) - compileAndGoGlobal = &script->global(); - Debugger::onNewScript(cx->asJSContext(), script, compileAndGoGlobal); + Debugger::onNewScript(cx->asJSContext(), script); } } diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index fa325d05fd0..a432d734e7c 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -4273,8 +4273,7 @@ JS::CloneAndExecuteScript(JSContext *cx, HandleObject obj, HandleScript scriptAr if (!script) return false; - Rooted global(cx, script->compileAndGo() ? &script->global() : nullptr); - js::Debugger::onNewScript(cx, script, global); + js::Debugger::onNewScript(cx, script); } return ExecuteScript(cx, obj, script, nullptr); } diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index 418c7555dd7..8ea3effba49 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -1090,10 +1090,8 @@ js::XDRScript(XDRState *xdr, HandleObject enclosingScope, HandleScript enc scriptp.set(script); /* see BytecodeEmitter::tellDebuggerAboutCompiledScript */ - if (!fun) { - RootedGlobalObject global(cx, script->compileAndGo() ? &script->global() : nullptr); - Debugger::onNewScript(cx, script, global); - } + if (!fun) + Debugger::onNewScript(cx, script); } return true; @@ -3191,8 +3189,7 @@ js::CloneFunctionScript(JSContext *cx, HandleFunction original, HandleFunction c cscript->setFunction(clone); script = clone->nonLazyScript(); - RootedGlobalObject global(cx, script->compileAndGo() ? &script->global() : nullptr); - Debugger::onNewScript(cx, script, global); + Debugger::onNewScript(cx, script); return true; } diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index fff4269f963..a901e784ee1 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -1302,47 +1302,27 @@ Debugger::dispatchHook(JSContext *cx, MutableHandleValue vp, Hook which, HandleO return JSTRAP_CONTINUE; } -static bool -AddNewScriptRecipients(GlobalObject::DebuggerVector *src, HandleScript script, - AutoValueVector *dest) -{ - bool wasEmpty = dest->length() == 0; - for (Debugger **p = src->begin(); p != src->end(); p++) { - Debugger *dbg = *p; - Value v = ObjectValue(*dbg->toJSObject()); - if (dbg->observesScript(script) && dbg->observesNewScript() && - (wasEmpty || Find(dest->begin(), dest->end(), v) == dest->end()) && - !dest->append(v)) - { - return false; - } - } - return true; -} - void -Debugger::slowPathOnNewScript(JSContext *cx, HandleScript script, GlobalObject *compileAndGoGlobal_) +Debugger::slowPathOnNewScript(JSContext *cx, HandleScript script) { - Rooted compileAndGoGlobal(cx, compileAndGoGlobal_); - - MOZ_ASSERT(script->compileAndGo() == !!compileAndGoGlobal); + Rooted global(cx, &script->global()); /* * Build the list of recipients based on the debuggers observing the * script's compartment. - * - * TODO bug 1064079 will simplify this logic. The meaning of - * compile-and-go has changed over the years and is no longer relevant to - * Debugger. */ AutoValueVector triggered(cx); - GlobalObject::DebuggerVector *debuggers = - (script->compileAndGo() - ? compileAndGoGlobal->getDebuggers() - : script->compartment()->maybeGlobal()->getDebuggers()); + GlobalObject::DebuggerVector *debuggers = global->getDebuggers(); if (debuggers) { - if (!AddNewScriptRecipients(debuggers, script, &triggered)) - return; + for (Debugger **p = debuggers->begin(); p != debuggers->end(); p++) { + Debugger *dbg = *p; + if (dbg->observesNewScript() && dbg->observesScript(script)) { + if (!triggered.append(ObjectValue(*dbg->toJSObject()))) { + js_ReportOutOfMemory(cx); + return; + } + } + } } /* @@ -1351,8 +1331,10 @@ Debugger::slowPathOnNewScript(JSContext *cx, HandleScript script, GlobalObject * */ for (Value *p = triggered.begin(); p != triggered.end(); p++) { Debugger *dbg = Debugger::fromJSObject(&p->toObject()); - if ((!compileAndGoGlobal || dbg->debuggees.has(compileAndGoGlobal)) && - dbg->enabled && dbg->getHook(OnNewScript)) { + if (dbg->debuggees.has(global) && + dbg->enabled && + dbg->getHook(OnNewScript)) + { dbg->fireNewScript(cx, script); } } diff --git a/js/src/vm/Debugger.h b/js/src/vm/Debugger.h index 51415a6eb35..0152023ff52 100644 --- a/js/src/vm/Debugger.h +++ b/js/src/vm/Debugger.h @@ -454,8 +454,7 @@ class Debugger : private mozilla::LinkedListElement static bool slowPathOnLeaveFrame(JSContext *cx, AbstractFramePtr frame, bool ok); static JSTrapStatus slowPathOnDebuggerStatement(JSContext *cx, AbstractFramePtr frame); static JSTrapStatus slowPathOnExceptionUnwind(JSContext *cx, AbstractFramePtr frame); - static void slowPathOnNewScript(JSContext *cx, HandleScript script, - GlobalObject *compileAndGoGlobal); + static void slowPathOnNewScript(JSContext *cx, HandleScript script); static void slowPathOnNewGlobalObject(JSContext *cx, Handle global); static bool slowPathOnLogAllocationSite(JSContext *cx, HandleSavedFrame frame, int64_t when, GlobalObject::DebuggerVector &dbgs); @@ -600,7 +599,7 @@ class Debugger : private mozilla::LinkedListElement */ static inline bool onLeaveFrame(JSContext *cx, AbstractFramePtr frame, bool ok); - static inline void onNewScript(JSContext *cx, HandleScript script, GlobalObject *compileAndGoGlobal); + static inline void onNewScript(JSContext *cx, HandleScript script); static inline void onNewGlobalObject(JSContext *cx, Handle global); static inline bool onLogAllocationSite(JSContext *cx, HandleSavedFrame frame, int64_t when); static JSTrapStatus onTrap(JSContext *cx, MutableHandleValue vp); @@ -878,18 +877,15 @@ Debugger::observesGlobal(GlobalObject *global) const } /* static */ void -Debugger::onNewScript(JSContext *cx, HandleScript script, GlobalObject *compileAndGoGlobal) +Debugger::onNewScript(JSContext *cx, HandleScript script) { - MOZ_ASSERT_IF(script->compileAndGo(), compileAndGoGlobal); - MOZ_ASSERT_IF(script->compileAndGo(), compileAndGoGlobal == &script->uninlinedGlobal()); // We early return in slowPathOnNewScript for self-hosted scripts, so we can // ignore those in our assertion here. MOZ_ASSERT_IF(!script->compartment()->options().invisibleToDebugger() && !script->selfHosted(), script->compartment()->firedOnNewGlobalObject); - MOZ_ASSERT_IF(!script->compileAndGo(), !compileAndGoGlobal); if (script->compartment()->isDebuggee()) - slowPathOnNewScript(cx, script, compileAndGoGlobal); + slowPathOnNewScript(cx, script); } /* static */ void diff --git a/js/src/vm/HelperThreads.cpp b/js/src/vm/HelperThreads.cpp index 057aa9bcfc0..8fbb0769f74 100644 --- a/js/src/vm/HelperThreads.cpp +++ b/js/src/vm/HelperThreads.cpp @@ -932,10 +932,7 @@ GlobalHelperThreadState::finishParseTask(JSContext *maybecx, JSRuntime *rt, void if (script) { // The Debugger only needs to be told about the topmost script that was compiled. - GlobalObject *compileAndGoGlobal = nullptr; - if (script->compileAndGo()) - compileAndGoGlobal = &script->global(); - Debugger::onNewScript(cx, script, compileAndGoGlobal); + Debugger::onNewScript(cx, script); // Update the compressed source table with the result. This is normally // called by setCompressedSource when compilation occurs on the main thread. From 0dcdc9d515c04e32a43329e427ed009def8d83e0 Mon Sep 17 00:00:00 2001 From: Dave Townsend Date: Fri, 30 Jan 2015 09:14:13 -0800 Subject: [PATCH 083/101] Bug 1127026: Shimmed AboutProtocolInstance.getURIFlags always returns null. r=billm --- toolkit/components/addoncompat/RemoteAddonsChild.jsm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toolkit/components/addoncompat/RemoteAddonsChild.jsm b/toolkit/components/addoncompat/RemoteAddonsChild.jsm index 3623244017e..ecce33c9237 100644 --- a/toolkit/components/addoncompat/RemoteAddonsChild.jsm +++ b/toolkit/components/addoncompat/RemoteAddonsChild.jsm @@ -287,7 +287,7 @@ AboutProtocolChannel.prototype = { function AboutProtocolInstance(contractID) { this._contractID = contractID; - this._uriFlags = null; + this._uriFlags = undefined; } AboutProtocolInstance.prototype = { From bcc24e85151b07513cb7807f038cd996e3dfb4b0 Mon Sep 17 00:00:00 2001 From: Nick Alexander Date: Mon, 26 Jan 2015 11:13:49 -0800 Subject: [PATCH 084/101] Bug 1123824 - Include platforms/android-VERSION in suggested mozconfig. r=me,f=psd DONTBUILD NPOTB --- python/mozboot/mozboot/debian.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/python/mozboot/mozboot/debian.py b/python/mozboot/mozboot/debian.py index 68dbb540cf6..d92aac74acc 100644 --- a/python/mozboot/mozboot/debian.py +++ b/python/mozboot/mozboot/debian.py @@ -121,7 +121,9 @@ class DebianBootstrapper(BaseBootstrapper): def suggest_mobile_android_mozconfig(self): import android - android.suggest_mozconfig(sdk_path=self.sdk_path, + # The SDK path that mozconfig wants includes platforms/android-21. + sdk_path = os.path.join(self.sdk_path, 'platforms', android.ANDROID_PLATFORM) + android.suggest_mozconfig(sdk_path=sdk_path, ndk_path=self.ndk_path) def _update_package_manager(self): From fe9b8a1926962a9463d9e2f8a805ac40779af9d6 Mon Sep 17 00:00:00 2001 From: Michael Comella Date: Thu, 29 Jan 2015 17:44:07 -0800 Subject: [PATCH 085/101] Bug 1090287 - Check that the selected tab is not null before updating progress visibility. r=rnewman --- mobile/android/base/toolbar/BrowserToolbar.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mobile/android/base/toolbar/BrowserToolbar.java b/mobile/android/base/toolbar/BrowserToolbar.java index c2128847eb5..fced287bf35 100644 --- a/mobile/android/base/toolbar/BrowserToolbar.java +++ b/mobile/android/base/toolbar/BrowserToolbar.java @@ -546,7 +546,11 @@ public abstract class BrowserToolbar extends ThemedRelativeLayout private void updateProgressVisibility() { final Tab selectedTab = Tabs.getInstance().getSelectedTab(); - updateProgressVisibility(selectedTab, selectedTab.getLoadProgress()); + // The selected tab may be null if GeckoApp (and thus the + // selected tab) are not yet initialized (bug 1090287). + if (selectedTab != null) { + updateProgressVisibility(selectedTab, selectedTab.getLoadProgress()); + } } private void updateProgressVisibility(Tab selectedTab, int progress) { From 3564f84577a24a539375e0e3bc99ca1ab14c36d4 Mon Sep 17 00:00:00 2001 From: Ryan VanderMeulen Date: Fri, 30 Jan 2015 15:08:06 -0500 Subject: [PATCH 086/101] Bug 1126941 - Update pdf.js to version 1.0.1130. r=Mossop, r=yury --- browser/extensions/pdfjs/README.mozilla | 2 +- .../pdfjs/content/PdfStreamConverter.jsm | 6 +- browser/extensions/pdfjs/content/build/pdf.js | 329 +- .../pdfjs/content/build/pdf.worker.js | 138 +- .../extensions/pdfjs/content/web/viewer.css | 19 +- .../extensions/pdfjs/content/web/viewer.js | 2668 +++++++++-------- 6 files changed, 1890 insertions(+), 1272 deletions(-) diff --git a/browser/extensions/pdfjs/README.mozilla b/browser/extensions/pdfjs/README.mozilla index 2c5a980c0dc..cb411f6aad9 100644 --- a/browser/extensions/pdfjs/README.mozilla +++ b/browser/extensions/pdfjs/README.mozilla @@ -1,4 +1,4 @@ This is the pdf.js project output, https://github.com/mozilla/pdf.js -Current extension version is: 1.0.1040 +Current extension version is: 1.0.1130 diff --git a/browser/extensions/pdfjs/content/PdfStreamConverter.jsm b/browser/extensions/pdfjs/content/PdfStreamConverter.jsm index 34507673896..11db211a1f5 100644 --- a/browser/extensions/pdfjs/content/PdfStreamConverter.jsm +++ b/browser/extensions/pdfjs/content/PdfStreamConverter.jsm @@ -349,7 +349,11 @@ ChromeActions.prototype = { return (!!prefBrowser && prefGfx); }, supportsDocumentColors: function() { - return getBoolPref('browser.display.use_document_colors', true); + if (getIntPref('browser.display.document_color_use', 0) === 2 || + !getBoolPref('browser.display.use_document_colors', true)) { + return false; + } + return true; }, reportTelemetry: function (data) { var probeInfo = JSON.parse(data); diff --git a/browser/extensions/pdfjs/content/build/pdf.js b/browser/extensions/pdfjs/content/build/pdf.js index 8ef617112f3..e1660d96e07 100644 --- a/browser/extensions/pdfjs/content/build/pdf.js +++ b/browser/extensions/pdfjs/content/build/pdf.js @@ -22,8 +22,8 @@ if (typeof PDFJS === 'undefined') { (typeof window !== 'undefined' ? window : this).PDFJS = {}; } -PDFJS.version = '1.0.1040'; -PDFJS.build = '997096f'; +PDFJS.version = '1.0.1130'; +PDFJS.build = 'e4f0ae2'; (function pdfjsWrapper() { // Use strict in our context only - users might not want it @@ -341,6 +341,7 @@ function isValidUrl(url, allowRelative) { case 'https': case 'ftp': case 'mailto': + case 'tel': return true; default: return false; @@ -470,6 +471,8 @@ var XRefParseException = (function XRefParseExceptionClosure() { function bytesToString(bytes) { + assert(bytes !== null && typeof bytes === 'object' && + bytes.length !== undefined, 'Invalid argument for bytesToString'); var length = bytes.length; var MAX_ARGUMENT_COUNT = 8192; if (length < MAX_ARGUMENT_COUNT) { @@ -485,6 +488,7 @@ function bytesToString(bytes) { } function stringToBytes(str) { + assert(typeof str === 'string', 'Invalid argument for stringToBytes'); var length = str.length; var bytes = new Uint8Array(length); for (var i = 0; i < length; ++i) { @@ -1374,6 +1378,9 @@ PDFJS.disableStream = (PDFJS.disableStream === undefined ? * Disable pre-fetching of PDF file data. When range requests are enabled PDF.js * will automatically keep fetching more data even if it isn't needed to display * the current page. This default behavior can be disabled. + * + * NOTE: It is also necessary to disable streaming, see above, + * in order for disabling of pre-fetching to work correctly. * @var {boolean} */ PDFJS.disableAutoFetch = (PDFJS.disableAutoFetch === undefined ? @@ -1437,7 +1444,9 @@ PDFJS.maxCanvasPixels = (PDFJS.maxCanvasPixels === undefined ? * * @typedef {Object} DocumentInitParameters * @property {string} url - The URL of the PDF. - * @property {TypedArray} data - A typed array with PDF data. + * @property {TypedArray|Array|string} data - Binary PDF data. Use typed arrays + * (Uint8Array) to improve the memory usage. If PDF data is BASE64-encoded, + * use atob() to convert it to a binary string first. * @property {Object} httpHeaders - Basic authentication headers. * @property {boolean} withCredentials - Indicates whether or not cross-site * Access-Control requests should be made using credentials such as cookies @@ -1446,6 +1455,9 @@ PDFJS.maxCanvasPixels = (PDFJS.maxCanvasPixels === undefined ? * @property {TypedArray} initialData - A typed array with the first portion or * all of the pdf data. Used by the extension since some data is already * loaded before the switch to range requests. + * @property {number} length - The PDF file length. It's used for progress + * reports and range requests operations. + * @property {PDFDataRangeTransport} range */ /** @@ -1462,68 +1474,226 @@ PDFJS.maxCanvasPixels = (PDFJS.maxCanvasPixels === undefined ? * is used, which means it must follow the same origin rules that any XHR does * e.g. No cross domain requests without CORS. * - * @param {string|TypedArray|DocumentInitParameters} source Can be a url to - * where a PDF is located, a typed array (Uint8Array) already populated with - * data or parameter object. + * @param {string|TypedArray|DocumentInitParameters|PDFDataRangeTransport} src + * Can be a url to where a PDF is located, a typed array (Uint8Array) + * already populated with data or parameter object. * - * @param {Object} pdfDataRangeTransport is optional. It is used if you want - * to manually serve range requests for data in the PDF. See viewer.js for - * an example of pdfDataRangeTransport's interface. + * @param {PDFDataRangeTransport} pdfDataRangeTransport (deprecated) It is used + * if you want to manually serve range requests for data in the PDF. * - * @param {function} passwordCallback is optional. It is used to request a + * @param {function} passwordCallback (deprecated) It is used to request a * password if wrong or no password was provided. The callback receives two * parameters: function that needs to be called with new password and reason * (see {PasswordResponses}). * - * @param {function} progressCallback is optional. It is used to be able to + * @param {function} progressCallback (deprecated) It is used to be able to * monitor the loading progress of the PDF file (necessary to implement e.g. * a loading bar). The callback receives an {Object} with the properties: * {number} loaded and {number} total. * - * @return {Promise} A promise that is resolved with {@link PDFDocumentProxy} - * object. + * @return {PDFDocumentLoadingTask} */ -PDFJS.getDocument = function getDocument(source, +PDFJS.getDocument = function getDocument(src, pdfDataRangeTransport, passwordCallback, progressCallback) { - var workerInitializedCapability, workerReadyCapability, transport; + var task = new PDFDocumentLoadingTask(); - if (typeof source === 'string') { - source = { url: source }; - } else if (isArrayBuffer(source)) { - source = { data: source }; - } else if (typeof source !== 'object') { - error('Invalid parameter in getDocument, need either Uint8Array, ' + - 'string or a parameter object'); + // Support of the obsolete arguments (for compatibility with API v1.0) + if (pdfDataRangeTransport) { + if (!(pdfDataRangeTransport instanceof PDFDataRangeTransport)) { + // Not a PDFDataRangeTransport instance, trying to add missing properties. + pdfDataRangeTransport = Object.create(pdfDataRangeTransport); + pdfDataRangeTransport.length = src.length; + pdfDataRangeTransport.initialData = src.initialData; + } + src = Object.create(src); + src.range = pdfDataRangeTransport; + } + task.onPassword = passwordCallback || null; + task.onProgress = progressCallback || null; + + var workerInitializedCapability, transport; + var source; + if (typeof src === 'string') { + source = { url: src }; + } else if (isArrayBuffer(src)) { + source = { data: src }; + } else if (src instanceof PDFDataRangeTransport) { + source = { range: src }; + } else { + if (typeof src !== 'object') { + error('Invalid parameter in getDocument, need either Uint8Array, ' + + 'string or a parameter object'); + } + if (!src.url && !src.data && !src.range) { + error('Invalid parameter object: need either .data, .range or .url'); + } + + source = src; } - if (!source.url && !source.data) { - error('Invalid parameter array, need either .data or .url'); - } - - // copy/use all keys as is except 'url' -- full path is required var params = {}; for (var key in source) { if (key === 'url' && typeof window !== 'undefined') { + // The full path is required in the 'url' field. params[key] = combineUrl(window.location.href, source[key]); continue; + } else if (key === 'range') { + continue; + } else if (key === 'data' && !(source[key] instanceof Uint8Array)) { + // Converting string or array-like data to Uint8Array. + var pdfBytes = source[key]; + if (typeof pdfBytes === 'string') { + params[key] = stringToBytes(pdfBytes); + } else if (typeof pdfBytes === 'object' && pdfBytes !== null && + !isNaN(pdfBytes.length)) { + params[key] = new Uint8Array(pdfBytes); + } else { + error('Invalid PDF binary data: either typed array, string or ' + + 'array-like object is expected in the data property.'); + } + continue; } params[key] = source[key]; } workerInitializedCapability = createPromiseCapability(); - workerReadyCapability = createPromiseCapability(); - transport = new WorkerTransport(workerInitializedCapability, - workerReadyCapability, pdfDataRangeTransport, - progressCallback); + transport = new WorkerTransport(workerInitializedCapability, source.range); workerInitializedCapability.promise.then(function transportInitialized() { - transport.passwordCallback = passwordCallback; - transport.fetchDocument(params); + transport.fetchDocument(task, params); }); - return workerReadyCapability.promise; + + return task; }; +/** + * PDF document loading operation. + * @class + */ +var PDFDocumentLoadingTask = (function PDFDocumentLoadingTaskClosure() { + /** @constructs PDFDocumentLoadingTask */ + function PDFDocumentLoadingTask() { + this._capability = createPromiseCapability(); + + /** + * Callback to request a password if wrong or no password was provided. + * The callback receives two parameters: function that needs to be called + * with new password and reason (see {PasswordResponses}). + */ + this.onPassword = null; + + /** + * Callback to be able to monitor the loading progress of the PDF file + * (necessary to implement e.g. a loading bar). The callback receives + * an {Object} with the properties: {number} loaded and {number} total. + */ + this.onProgress = null; + } + + PDFDocumentLoadingTask.prototype = + /** @lends PDFDocumentLoadingTask.prototype */ { + /** + * @return {Promise} + */ + get promise() { + return this._capability.promise; + }, + + // TODO add cancel or abort method + + /** + * Registers callbacks to indicate the document loading completion. + * + * @param {function} onFulfilled The callback for the loading completion. + * @param {function} onRejected The callback for the loading failure. + * @return {Promise} A promise that is resolved after the onFulfilled or + * onRejected callback. + */ + then: function PDFDocumentLoadingTask_then(onFulfilled, onRejected) { + return this.promise.then.apply(this.promise, arguments); + } + }; + + return PDFDocumentLoadingTask; +})(); + +/** + * Abstract class to support range requests file loading. + * @class + */ +var PDFDataRangeTransport = (function pdfDataRangeTransportClosure() { + /** + * @constructs PDFDataRangeTransport + * @param {number} length + * @param {Uint8Array} initialData + */ + function PDFDataRangeTransport(length, initialData) { + this.length = length; + this.initialData = initialData; + + this._rangeListeners = []; + this._progressListeners = []; + this._progressiveReadListeners = []; + this._readyCapability = createPromiseCapability(); + } + PDFDataRangeTransport.prototype = + /** @lends PDFDataRangeTransport.prototype */ { + addRangeListener: + function PDFDataRangeTransport_addRangeListener(listener) { + this._rangeListeners.push(listener); + }, + + addProgressListener: + function PDFDataRangeTransport_addProgressListener(listener) { + this._progressListeners.push(listener); + }, + + addProgressiveReadListener: + function PDFDataRangeTransport_addProgressiveReadListener(listener) { + this._progressiveReadListeners.push(listener); + }, + + onDataRange: function PDFDataRangeTransport_onDataRange(begin, chunk) { + var listeners = this._rangeListeners; + for (var i = 0, n = listeners.length; i < n; ++i) { + listeners[i](begin, chunk); + } + }, + + onDataProgress: function PDFDataRangeTransport_onDataProgress(loaded) { + this._readyCapability.promise.then(function () { + var listeners = this._progressListeners; + for (var i = 0, n = listeners.length; i < n; ++i) { + listeners[i](loaded); + } + }.bind(this)); + }, + + onDataProgressiveRead: + function PDFDataRangeTransport_onDataProgress(chunk) { + this._readyCapability.promise.then(function () { + var listeners = this._progressiveReadListeners; + for (var i = 0, n = listeners.length; i < n; ++i) { + listeners[i](chunk); + } + }.bind(this)); + }, + + transportReady: function PDFDataRangeTransport_transportReady() { + this._readyCapability.resolve(); + }, + + requestDataRange: + function PDFDataRangeTransport_requestDataRange(begin, end) { + throw new Error('Abstract method PDFDataRangeTransport.requestDataRange'); + } + }; + return PDFDataRangeTransport; +})(); + +PDFJS.PDFDataRangeTransport = PDFDataRangeTransport; + /** * Proxy to a PDFDocument in the worker thread. Also, contains commonly used * properties that can be read synchronously. @@ -1639,7 +1809,7 @@ var PDFDocumentProxy = (function PDFDocumentProxyClosure() { return this.transport.downloadInfoCapability.promise; }, /** - * @returns {Promise} A promise this is resolved with current stats about + * @return {Promise} A promise this is resolved with current stats about * document structures (see {@link PDFDocumentStats}). */ getStats: function PDFDocumentProxy_getStats() { @@ -1703,7 +1873,7 @@ var PDFDocumentProxy = (function PDFDocumentProxyClosure() { * (default value is 'display'). * @property {Object} imageLayer - (optional) An object that has beginLayout, * endLayout and appendImage functions. - * @property {function} continueCallback - (optional) A function that will be + * @property {function} continueCallback - (deprecated) A function that will be * called each time the rendering is paused. To continue * rendering call the function that is the first argument * to the callback. @@ -1836,7 +2006,12 @@ var PDFPageProxy = (function PDFPageProxyClosure() { intentState.renderTasks = []; } intentState.renderTasks.push(internalRenderTask); - var renderTask = new RenderTask(internalRenderTask); + var renderTask = internalRenderTask.task; + + // Obsolete parameter support + if (params.continueCallback) { + renderTask.onContinue = params.continueCallback; + } var self = this; intentState.displayReadyCapability.promise.then( @@ -2001,19 +2176,16 @@ var PDFPageProxy = (function PDFPageProxyClosure() { * @ignore */ var WorkerTransport = (function WorkerTransportClosure() { - function WorkerTransport(workerInitializedCapability, workerReadyCapability, - pdfDataRangeTransport, progressCallback) { + function WorkerTransport(workerInitializedCapability, pdfDataRangeTransport) { this.pdfDataRangeTransport = pdfDataRangeTransport; - this.workerInitializedCapability = workerInitializedCapability; - this.workerReadyCapability = workerReadyCapability; - this.progressCallback = progressCallback; this.commonObjs = new PDFObjects(); + this.loadingTask = null; + this.pageCache = []; this.pagePromises = []; this.downloadInfoCapability = createPromiseCapability(); - this.passwordCallback = null; // If worker support isn't disabled explicit and the browser has worker // support, create a new web worker and test if it/the browser fullfills @@ -2152,48 +2324,50 @@ var WorkerTransport = (function WorkerTransportClosure() { this.numPages = data.pdfInfo.numPages; var pdfDocument = new PDFDocumentProxy(pdfInfo, this); this.pdfDocument = pdfDocument; - this.workerReadyCapability.resolve(pdfDocument); + this.loadingTask._capability.resolve(pdfDocument); }, this); messageHandler.on('NeedPassword', function transportNeedPassword(exception) { - if (this.passwordCallback) { - return this.passwordCallback(updatePassword, - PasswordResponses.NEED_PASSWORD); + var loadingTask = this.loadingTask; + if (loadingTask.onPassword) { + return loadingTask.onPassword(updatePassword, + PasswordResponses.NEED_PASSWORD); } - this.workerReadyCapability.reject( + loadingTask._capability.reject( new PasswordException(exception.message, exception.code)); }, this); messageHandler.on('IncorrectPassword', function transportIncorrectPassword(exception) { - if (this.passwordCallback) { - return this.passwordCallback(updatePassword, - PasswordResponses.INCORRECT_PASSWORD); + var loadingTask = this.loadingTask; + if (loadingTask.onPassword) { + return loadingTask.onPassword(updatePassword, + PasswordResponses.INCORRECT_PASSWORD); } - this.workerReadyCapability.reject( + loadingTask._capability.reject( new PasswordException(exception.message, exception.code)); }, this); messageHandler.on('InvalidPDF', function transportInvalidPDF(exception) { - this.workerReadyCapability.reject( + this.loadingTask._capability.reject( new InvalidPDFException(exception.message)); }, this); messageHandler.on('MissingPDF', function transportMissingPDF(exception) { - this.workerReadyCapability.reject( + this.loadingTask._capability.reject( new MissingPDFException(exception.message)); }, this); messageHandler.on('UnexpectedResponse', function transportUnexpectedResponse(exception) { - this.workerReadyCapability.reject( + this.loadingTask._capability.reject( new UnexpectedResponseException(exception.message, exception.status)); }, this); messageHandler.on('UnknownError', function transportUnknownError(exception) { - this.workerReadyCapability.reject( + this.loadingTask._capability.reject( new UnknownErrorException(exception.message, exception.details)); }, this); @@ -2288,8 +2462,9 @@ var WorkerTransport = (function WorkerTransportClosure() { }, this); messageHandler.on('DocProgress', function transportDocProgress(data) { - if (this.progressCallback) { - this.progressCallback({ + var loadingTask = this.loadingTask; + if (loadingTask.onProgress) { + loadingTask.onProgress({ loaded: data.loaded, total: data.total }); @@ -2349,10 +2524,16 @@ var WorkerTransport = (function WorkerTransportClosure() { }); }, - fetchDocument: function WorkerTransport_fetchDocument(source) { + fetchDocument: function WorkerTransport_fetchDocument(loadingTask, source) { + this.loadingTask = loadingTask; + source.disableAutoFetch = PDFJS.disableAutoFetch; source.disableStream = PDFJS.disableStream; source.chunkedViewerLoading = !!this.pdfDataRangeTransport; + if (this.pdfDataRangeTransport) { + source.length = this.pdfDataRangeTransport.length; + source.initialData = this.pdfDataRangeTransport.initialData; + } this.messageHandler.send('GetDocRequest', { source: source, disableRange: PDFJS.disableRange, @@ -2563,26 +2744,37 @@ var PDFObjects = (function PDFObjectsClosure() { */ var RenderTask = (function RenderTaskClosure() { function RenderTask(internalRenderTask) { - this.internalRenderTask = internalRenderTask; + this._internalRenderTask = internalRenderTask; + /** - * Promise for rendering task completion. - * @type {Promise} + * Callback for incremental rendering -- a function that will be called + * each time the rendering is paused. To continue rendering call the + * function that is the first argument to the callback. + * @type {function} */ - this.promise = this.internalRenderTask.capability.promise; + this.onContinue = null; } RenderTask.prototype = /** @lends RenderTask.prototype */ { + /** + * Promise for rendering task completion. + * @return {Promise} + */ + get promise() { + return this._internalRenderTask.capability.promise; + }, + /** * Cancels the rendering task. If the task is currently rendering it will * not be cancelled until graphics pauses with a timeout. The promise that * this object extends will resolved when cancelled. */ cancel: function RenderTask_cancel() { - this.internalRenderTask.cancel(); + this._internalRenderTask.cancel(); }, /** - * Registers callback to indicate the rendering task completion. + * Registers callbacks to indicate the rendering task completion. * * @param {function} onFulfilled The callback for the rendering completion. * @param {function} onRejected The callback for the rendering failure. @@ -2590,7 +2782,7 @@ var RenderTask = (function RenderTaskClosure() { * onRejected callback. */ then: function RenderTask_then(onFulfilled, onRejected) { - return this.promise.then(onFulfilled, onRejected); + return this.promise.then.apply(this.promise, arguments); } }; @@ -2617,6 +2809,7 @@ var InternalRenderTask = (function InternalRenderTaskClosure() { this.graphicsReady = false; this.cancelled = false; this.capability = createPromiseCapability(); + this.task = new RenderTask(this); // caching this-bound methods this._continueBound = this._continue.bind(this); this._scheduleNextBound = this._scheduleNext.bind(this); @@ -2679,8 +2872,8 @@ var InternalRenderTask = (function InternalRenderTaskClosure() { if (this.cancelled) { return; } - if (this.params.continueCallback) { - this.params.continueCallback(this._scheduleNextBound); + if (this.task.onContinue) { + this.task.onContinue.call(this.task, this._scheduleNextBound); } else { this._scheduleNext(); } diff --git a/browser/extensions/pdfjs/content/build/pdf.worker.js b/browser/extensions/pdfjs/content/build/pdf.worker.js index 0db3119f503..c69e081395e 100644 --- a/browser/extensions/pdfjs/content/build/pdf.worker.js +++ b/browser/extensions/pdfjs/content/build/pdf.worker.js @@ -22,8 +22,8 @@ if (typeof PDFJS === 'undefined') { (typeof window !== 'undefined' ? window : this).PDFJS = {}; } -PDFJS.version = '1.0.1040'; -PDFJS.build = '997096f'; +PDFJS.version = '1.0.1130'; +PDFJS.build = 'e4f0ae2'; (function pdfjsWrapper() { // Use strict in our context only - users might not want it @@ -341,6 +341,7 @@ function isValidUrl(url, allowRelative) { case 'https': case 'ftp': case 'mailto': + case 'tel': return true; default: return false; @@ -470,6 +471,8 @@ var XRefParseException = (function XRefParseExceptionClosure() { function bytesToString(bytes) { + assert(bytes !== null && typeof bytes === 'object' && + bytes.length !== undefined, 'Invalid argument for bytesToString'); var length = bytes.length; var MAX_ARGUMENT_COUNT = 8192; if (length < MAX_ARGUMENT_COUNT) { @@ -485,6 +488,7 @@ function bytesToString(bytes) { } function stringToBytes(str) { + assert(typeof str === 'string', 'Invalid argument for stringToBytes'); var length = str.length; var bytes = new Uint8Array(length); for (var i = 0; i < length; ++i) { @@ -1449,6 +1453,9 @@ var ChunkedStream = (function ChunkedStreamClosure() { getUint16: function ChunkedStream_getUint16() { var b0 = this.getByte(); var b1 = this.getByte(); + if (b0 === -1 || b1 === -1) { + return -1; + } return (b0 << 8) + b1; }, @@ -14056,7 +14063,7 @@ var SpecialPUASymbols = { '63731': 0x23A9, // braceleftbt (0xF8F3) '63740': 0x23AB, // bracerighttp (0xF8FC) '63741': 0x23AC, // bracerightmid (0xF8FD) - '63742': 0x23AD, // bracerightmid (0xF8FE) + '63742': 0x23AD, // bracerightbt (0xF8FE) '63726': 0x23A1, // bracketlefttp (0xF8EE) '63727': 0x23A2, // bracketleftex (0xF8EF) '63728': 0x23A3, // bracketleftbt (0xF8F0) @@ -15996,7 +16003,7 @@ var Font = (function FontClosure() { // to be used with the canvas.font. var fontName = name.replace(/[,_]/g, '-'); var isStandardFont = !!stdFontMap[fontName] || - (nonStdFontMap[fontName] && !!stdFontMap[nonStdFontMap[fontName]]); + !!(nonStdFontMap[fontName] && stdFontMap[nonStdFontMap[fontName]]); fontName = stdFontMap[fontName] || nonStdFontMap[fontName] || fontName; this.bold = (fontName.search(/bold/gi) !== -1); @@ -29595,6 +29602,102 @@ var Parser = (function ParserClosure() { } return ((stream.pos - 4) - startPos); }, + /** + * Find the EOI (end-of-image) marker 0xFFD9 of the stream. + * @returns {number} The inline stream length. + */ + findDCTDecodeInlineStreamEnd: + function Parser_findDCTDecodeInlineStreamEnd(stream) { + var startPos = stream.pos, foundEOI = false, b, markerLength, length; + while ((b = stream.getByte()) !== -1) { + if (b !== 0xFF) { // Not a valid marker. + continue; + } + switch (stream.getByte()) { + case 0x00: // Byte stuffing. + // 0xFF00 appears to be a very common byte sequence in JPEG images. + break; + + case 0xFF: // Fill byte. + // Avoid skipping a valid marker, resetting the stream position. + stream.skip(-1); + break; + + case 0xD9: // EOI + foundEOI = true; + break; + + case 0xC0: // SOF0 + case 0xC1: // SOF1 + case 0xC2: // SOF2 + case 0xC3: // SOF3 + + case 0xC5: // SOF5 + case 0xC6: // SOF6 + case 0xC7: // SOF7 + + case 0xC9: // SOF9 + case 0xCA: // SOF10 + case 0xCB: // SOF11 + + case 0xCD: // SOF13 + case 0xCE: // SOF14 + case 0xCF: // SOF15 + + case 0xC4: // DHT + case 0xCC: // DAC + + case 0xDA: // SOS + case 0xDB: // DQT + case 0xDC: // DNL + case 0xDD: // DRI + case 0xDE: // DHP + case 0xDF: // EXP + + case 0xE0: // APP0 + case 0xE1: // APP1 + case 0xE2: // APP2 + case 0xE3: // APP3 + case 0xE4: // APP4 + case 0xE5: // APP5 + case 0xE6: // APP6 + case 0xE7: // APP7 + case 0xE8: // APP8 + case 0xE9: // APP9 + case 0xEA: // APP10 + case 0xEB: // APP11 + case 0xEC: // APP12 + case 0xED: // APP13 + case 0xEE: // APP14 + case 0xEF: // APP15 + + case 0xFE: // COM + // The marker should be followed by the length of the segment. + markerLength = stream.getUint16(); + if (markerLength > 2) { + // |markerLength| contains the byte length of the marker segment, + // including its own length (2 bytes) and excluding the marker. + stream.skip(markerLength - 2); // Jump to the next marker. + } else { + // The marker length is invalid, resetting the stream position. + stream.skip(-2); + } + break; + } + if (foundEOI) { + break; + } + } + length = stream.pos - startPos; + if (b === -1) { + warn('Inline DCTDecode image stream: ' + + 'EOI marker not found, searching for /EI/ instead.'); + stream.skip(-length); // Reset the stream position. + return this.findDefaultInlineStreamEnd(stream); + } + this.inlineStreamSkipEI(stream); + return length; + }, /** * Find the EOD (end-of-data) marker '~>' (i.e. TILDE + GT) of the stream. * @returns {number} The inline stream length. @@ -29686,7 +29789,9 @@ var Parser = (function ParserClosure() { // Parse image stream. var startPos = stream.pos, length, i, ii; - if (filterName === 'ASCII85Decide' || filterName === 'A85') { + if (filterName === 'DCTDecode' || filterName === 'DCT') { + length = this.findDCTDecodeInlineStreamEnd(stream); + } else if (filterName === 'ASCII85Decide' || filterName === 'A85') { length = this.findASCII85DecodeInlineStreamEnd(stream); } else if (filterName === 'ASCIIHexDecode' || filterName === 'AHx') { length = this.findASCIIHexDecodeInlineStreamEnd(stream); @@ -30613,6 +30718,9 @@ var Stream = (function StreamClosure() { getUint16: function Stream_getUint16() { var b0 = this.getByte(); var b1 = this.getByte(); + if (b0 === -1 || b1 === -1) { + return -1; + } return (b0 << 8) + b1; }, getInt32: function Stream_getInt32() { @@ -30740,6 +30848,9 @@ var DecodeStream = (function DecodeStreamClosure() { getUint16: function DecodeStream_getUint16() { var b0 = this.getByte(); var b1 = this.getByte(); + if (b0 === -1 || b1 === -1) { + return -1; + } return (b0 << 8) + b1; }, getInt32: function DecodeStream_getInt32() { @@ -34839,11 +34950,6 @@ var JpxImage = (function JpxImageClosure() { context.QCC = []; context.COC = []; break; - case 0xFF55: // Tile-part lengths, main header (TLM) - var Ltlm = readUint16(data, position); // Marker segment length - // Skip tile length markers - position += Ltlm; - break; case 0xFF5C: // Quantization default (QCD) length = readUint16(data, position); var qcd = {}; @@ -35033,6 +35139,9 @@ var JpxImage = (function JpxImageClosure() { length = tile.dataEnd - position; parseTilePackets(context, data, position, length); break; + case 0xFF55: // Tile-part lengths, main header (TLM) + case 0xFF57: // Packet length, main header (PLM) + case 0xFF58: // Packet length, tile-part header (PLT) case 0xFF64: // Comment (COM) length = readUint16(data, position); // skipping content @@ -35373,7 +35482,7 @@ var JpxImage = (function JpxImageClosure() { r = 0; c = 0; p = 0; - + this.nextPacket = function JpxImage_nextPacket() { // Section B.12.1.3 Resolution-position-component-layer for (; r <= maxDecompositionLevelsCount; r++) { @@ -35457,7 +35566,7 @@ var JpxImage = (function JpxImageClosure() { var componentsCount = siz.Csiz; var precinctsSizes = getPrecinctSizesInImageScale(tile); var l = 0, r = 0, c = 0, px = 0, py = 0; - + this.nextPacket = function JpxImage_nextPacket() { // Section B.12.1.5 Component-position-resolution-layer for (; c < componentsCount; ++c) { @@ -37030,10 +37139,9 @@ var Jbig2Image = (function Jbig2ImageClosure() { // At each pixel: Clear contextLabel pixels that are shifted // out of the context, then add new ones. - // If j + n is out of range at the right image border, then - // the undefined value of bitmap[i - 2][j + n] is shifted to 0 contextLabel = ((contextLabel & OLD_PIXEL_MASK) << 1) | - (row2[j + 3] << 11) | (row1[j + 4] << 4) | pixel; + (j + 3 < width ? row2[j + 3] << 11 : 0) | + (j + 4 < width ? row1[j + 4] << 4 : 0) | pixel; } } diff --git a/browser/extensions/pdfjs/content/web/viewer.css b/browser/extensions/pdfjs/content/web/viewer.css index 3ccd604aa81..b51e608e9f4 100644 --- a/browser/extensions/pdfjs/content/web/viewer.css +++ b/browser/extensions/pdfjs/content/web/viewer.css @@ -20,6 +20,7 @@ right: 0; bottom: 0; overflow: hidden; + opacity: 0.2; } .textLayer > div { @@ -55,6 +56,9 @@ background-color: rgb(0, 100, 0); } +.textLayer ::selection { background: rgb(0,0,255); } +.textLayer ::-moz-selection { background: rgb(0,0,255); } + .pdfViewer .canvasWrapper { overflow: hidden; } @@ -1153,9 +1157,13 @@ html[dir='rtl'] .verticalToolbarSeparator { margin-bottom: 10px; } +#thumbnailView > a:last-of-type > .thumbnail:not([data-loaded]) { + margin-bottom: 9px; +} + .thumbnail:not([data-loaded]) { border: 1px dashed rgba(255, 255, 255, 0.5); - margin-bottom: 10px; + margin: -1px -1px 4px -1px; } .thumbnailImage { @@ -1164,6 +1172,8 @@ html[dir='rtl'] .verticalToolbarSeparator { box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5), 0 2px 8px rgba(0, 0, 0, 0.3); opacity: 0.8; z-index: 99; + background-color: white; + background-clip: content-box; } .thumbnailSelectionRing { @@ -1294,19 +1304,12 @@ html[dir='rtl'] .attachmentsItem > button { cursor: default; } - /* TODO: file FF bug to support ::-moz-selection:window-inactive so we can override the opaque grey background when the window is inactive; see https://bugzilla.mozilla.org/show_bug.cgi?id=706209 */ ::selection { background: rgba(0,0,255,0.3); } ::-moz-selection { background: rgba(0,0,255,0.3); } -.textLayer ::selection { background: rgb(0,0,255); } -.textLayer ::-moz-selection { background: rgb(0,0,255); } -.textLayer { - opacity: 0.2; -} - #errorWrapper { background: none repeat scroll 0 0 #FF5555; color: white; diff --git a/browser/extensions/pdfjs/content/web/viewer.js b/browser/extensions/pdfjs/content/web/viewer.js index 16cb6113e5e..991c191229d 100644 --- a/browser/extensions/pdfjs/content/web/viewer.js +++ b/browser/extensions/pdfjs/content/web/viewer.js @@ -16,10 +16,10 @@ */ /* globals PDFJS, PDFBug, FirefoxCom, Stats, Cache, ProgressBar, DownloadManager, getFileName, scrollIntoView, getPDFFileNameFromURL, - PDFHistory, Preferences, SidebarView, ViewHistory, PageView, + PDFHistory, Preferences, SidebarView, ViewHistory, Stats, PDFThumbnailViewer, URL, noContextMenuHandler, SecondaryToolbar, PasswordPrompt, PresentationMode, HandTool, Promise, - DocumentProperties, DocumentOutlineView, DocumentAttachmentsView, + DocumentProperties, PDFOutlineView, PDFAttachmentView, OverlayManager, PDFFindController, PDFFindBar, getVisibleElements, watchScroll, PDFViewer, PDFRenderingQueue, PresentationModeState, RenderingStates, DEFAULT_SCALE, UNKNOWN_SCALE, @@ -51,7 +51,6 @@ var UNKNOWN_SCALE = 0; var MAX_AUTO_SCALE = 1.25; var SCROLLBAR_PADDING = 40; var VERTICAL_PADDING = 5; -var DEFAULT_CACHE_SIZE = 10; // optimised CSS custom property getter/setter var CustomStyle = (function CustomStyleClosure() { @@ -379,26 +378,6 @@ var ProgressBar = (function ProgressBarClosure() { return ProgressBar; })(); -var Cache = function cacheCache(size) { - var data = []; - this.push = function cachePush(view) { - var i = data.indexOf(view); - if (i >= 0) { - data.splice(i, 1); - } - data.push(view); - if (data.length > size) { - data.shift().destroy(); - } - }; - this.resize = function (newSize) { - size = newSize; - while (data.length > size) { - data.shift().destroy(); - } - }; -}; - var DEFAULT_PREFERENCES = { @@ -931,6 +910,9 @@ var FindStates = { FIND_PENDING: 3 }; +var FIND_SCROLL_OFFSET_TOP = -50; +var FIND_SCROLL_OFFSET_LEFT = -400; + /** * Provides "search" or "find" functionality for the PDF. * This object actually performs the search for a given string. @@ -1104,8 +1086,6 @@ var PDFFindController = (function PDFFindControllerClosure() { }, updatePage: function PDFFindController_updatePage(index) { - var page = this.pdfViewer.getPageView(index); - if (this.selected.pageIdx === index) { // If the page is selected, scroll the page into view, which triggers // rendering the page, which adds the textLayer. Once the textLayer is @@ -1113,6 +1093,7 @@ var PDFFindController = (function PDFFindControllerClosure() { this.pdfViewer.scrollPageIntoView(index + 1); } + var page = this.pdfViewer.getPageView(index); if (page.textLayer) { page.textLayer.updateMatches(); } @@ -1216,6 +1197,26 @@ var PDFFindController = (function PDFFindControllerClosure() { } }, + /** + * The method is called back from the text layer when match presentation + * is updated. + * @param {number} pageIndex - page index. + * @param {number} index - match index. + * @param {Array} elements - text layer div elements array. + * @param {number} beginIdx - start index of the div array for the match. + * @param {number} endIdx - end index of the div array for the match. + */ + updateMatchPosition: function PDFFindController_updateMatchPosition( + pageIndex, index, elements, beginIdx, endIdx) { + if (this.selected.matchIdx === index && + this.selected.pageIdx === pageIndex) { + scrollIntoView(elements[beginIdx], { + top: FIND_SCROLL_OFFSET_TOP, + left: FIND_SCROLL_OFFSET_LEFT + }); + } + }, + nextPageMatch: function PDFFindController_nextPageMatch() { if (this.resumePageIdx !== null) { console.error('There can only be one pending page.'); @@ -2672,6 +2673,7 @@ var PresentationModeState = { }; var IGNORE_CURRENT_POSITION_ON_ZOOM = false; +var DEFAULT_CACHE_SIZE = 10; var CLEANUP_TIMEOUT = 30000; @@ -2822,7 +2824,10 @@ var PDFRenderingQueue = (function PDFRenderingQueueClosure() { break; case RenderingStates.INITIAL: this.highestPriorityPage = view.renderingId; - view.draw(this.renderHighestPriority.bind(this)); + var continueRendering = function () { + this.renderHighestPriority(); + }.bind(this); + view.draw().then(continueRendering, continueRendering); break; } return true; @@ -2833,601 +2838,525 @@ var PDFRenderingQueue = (function PDFRenderingQueueClosure() { })(); +var TEXT_LAYER_RENDER_DELAY = 200; // ms + /** - * @constructor - * @param {HTMLDivElement} container - The viewer element. - * @param {number} id - The page unique ID (normally its number). - * @param {number} scale - The page scale display. - * @param {PageViewport} defaultViewport - The page viewport. - * @param {IPDFLinkService} linkService - The navigation/linking service. - * @param {PDFRenderingQueue} renderingQueue - The rendering queue object. - * @param {Cache} cache - The page cache. - * @param {PDFPageSource} pageSource - * @param {PDFViewer} viewer - * + * @typedef {Object} PDFPageViewOptions + * @property {HTMLDivElement} container - The viewer element. + * @property {number} id - The page unique ID (normally its number). + * @property {number} scale - The page scale display. + * @property {PageViewport} defaultViewport - The page viewport. + * @property {PDFRenderingQueue} renderingQueue - The rendering queue object. + * @property {IPDFTextLayerFactory} textLayerFactory + * @property {IPDFAnnotationsLayerFactory} annotationsLayerFactory + */ + +/** + * @class * @implements {IRenderableView} */ -var PageView = function pageView(container, id, scale, defaultViewport, - linkService, renderingQueue, cache, - pageSource, viewer) { - this.id = id; - this.renderingId = 'page' + id; +var PDFPageView = (function PDFPageViewClosure() { + /** + * @constructs PDFPageView + * @param {PDFPageViewOptions} options + */ + function PDFPageView(options) { + var container = options.container; + var id = options.id; + var scale = options.scale; + var defaultViewport = options.defaultViewport; + var renderingQueue = options.renderingQueue; + var textLayerFactory = options.textLayerFactory; + var annotationsLayerFactory = options.annotationsLayerFactory; - this.rotation = 0; - this.scale = scale || 1.0; - this.viewport = defaultViewport; - this.pdfPageRotate = defaultViewport.rotation; - this.hasRestrictedScaling = false; + this.id = id; + this.renderingId = 'page' + id; - this.linkService = linkService; - this.renderingQueue = renderingQueue; - this.cache = cache; - this.pageSource = pageSource; - this.viewer = viewer; + this.rotation = 0; + this.scale = scale || 1.0; + this.viewport = defaultViewport; + this.pdfPageRotate = defaultViewport.rotation; + this.hasRestrictedScaling = false; - this.renderingState = RenderingStates.INITIAL; - this.resume = null; + this.renderingQueue = renderingQueue; + this.textLayerFactory = textLayerFactory; + this.annotationsLayerFactory = annotationsLayerFactory; - this.textLayer = null; - - this.zoomLayer = null; - - this.annotationLayer = null; - - var anchor = document.createElement('a'); - anchor.name = '' + this.id; - - var div = this.el = document.createElement('div'); - div.id = 'pageContainer' + this.id; - div.className = 'page'; - div.style.width = Math.floor(this.viewport.width) + 'px'; - div.style.height = Math.floor(this.viewport.height) + 'px'; - - container.appendChild(anchor); - container.appendChild(div); - - this.setPdfPage = function pageViewSetPdfPage(pdfPage) { - this.pdfPage = pdfPage; - this.pdfPageRotate = pdfPage.rotate; - var totalRotation = (this.rotation + this.pdfPageRotate) % 360; - this.viewport = pdfPage.getViewport(this.scale * CSS_UNITS, totalRotation); - this.stats = pdfPage.stats; - this.reset(); - }; - - this.destroy = function pageViewDestroy() { - this.zoomLayer = null; - this.reset(); - if (this.pdfPage) { - this.pdfPage.destroy(); - } - }; - - this.reset = function pageViewReset(keepAnnotations) { - if (this.renderTask) { - this.renderTask.cancel(); - } - this.resume = null; this.renderingState = RenderingStates.INITIAL; + this.resume = null; + this.onBeforeDraw = null; + this.onAfterDraw = null; + + this.textLayer = null; + + this.zoomLayer = null; + + this.annotationLayer = null; + + var div = document.createElement('div'); + div.id = 'pageContainer' + this.id; + div.className = 'page'; div.style.width = Math.floor(this.viewport.width) + 'px'; div.style.height = Math.floor(this.viewport.height) + 'px'; + this.el = div; // TODO replace 'el' property usage + this.div = div; - var childNodes = div.childNodes; - for (var i = div.childNodes.length - 1; i >= 0; i--) { - var node = childNodes[i]; - if ((this.zoomLayer && this.zoomLayer === node) || - (keepAnnotations && this.annotationLayer === node)) { - continue; - } - div.removeChild(node); - } - div.removeAttribute('data-loaded'); - - if (keepAnnotations) { - if (this.annotationLayer) { - // Hide annotationLayer until all elements are resized - // so they are not displayed on the already-resized page - this.annotationLayer.setAttribute('hidden', 'true'); - } - } else { - this.annotationLayer = null; - } - - if (this.canvas) { - // Zeroing the width and height causes Firefox to release graphics - // resources immediately, which can greatly reduce memory consumption. - this.canvas.width = 0; - this.canvas.height = 0; - delete this.canvas; - } - - this.loadingIconDiv = document.createElement('div'); - this.loadingIconDiv.className = 'loadingIcon'; - div.appendChild(this.loadingIconDiv); - }; - - this.update = function pageViewUpdate(scale, rotation) { - this.scale = scale || this.scale; - - if (typeof rotation !== 'undefined') { - this.rotation = rotation; - } - - var totalRotation = (this.rotation + this.pdfPageRotate) % 360; - this.viewport = this.viewport.clone({ - scale: this.scale * CSS_UNITS, - rotation: totalRotation - }); - - var isScalingRestricted = false; - if (this.canvas && PDFJS.maxCanvasPixels > 0) { - var ctx = this.canvas.getContext('2d'); - var outputScale = getOutputScale(ctx); - var pixelsInViewport = this.viewport.width * this.viewport.height; - var maxScale = Math.sqrt(PDFJS.maxCanvasPixels / pixelsInViewport); - if (((Math.floor(this.viewport.width) * outputScale.sx) | 0) * - ((Math.floor(this.viewport.height) * outputScale.sy) | 0) > - PDFJS.maxCanvasPixels) { - isScalingRestricted = true; - } - } - - if (this.canvas && - (PDFJS.useOnlyCssZoom || - (this.hasRestrictedScaling && isScalingRestricted))) { - this.cssTransform(this.canvas, true); - return; - } else if (this.canvas && !this.zoomLayer) { - this.zoomLayer = this.canvas.parentNode; - this.zoomLayer.style.position = 'absolute'; - } - if (this.zoomLayer) { - this.cssTransform(this.zoomLayer.firstChild); - } - this.reset(true); - }; - - this.cssTransform = function pageCssTransform(canvas, redrawAnnotations) { - // Scale canvas, canvas wrapper, and page container. - var width = this.viewport.width; - var height = this.viewport.height; - canvas.style.width = canvas.parentNode.style.width = div.style.width = - Math.floor(width) + 'px'; - canvas.style.height = canvas.parentNode.style.height = div.style.height = - Math.floor(height) + 'px'; - // The canvas may have been originally rotated, so rotate relative to that. - var relativeRotation = this.viewport.rotation - canvas._viewport.rotation; - var absRotation = Math.abs(relativeRotation); - var scaleX = 1, scaleY = 1; - if (absRotation === 90 || absRotation === 270) { - // Scale x and y because of the rotation. - scaleX = height / width; - scaleY = width / height; - } - var cssTransform = 'rotate(' + relativeRotation + 'deg) ' + - 'scale(' + scaleX + ',' + scaleY + ')'; - CustomStyle.setProp('transform', canvas, cssTransform); - - if (this.textLayer) { - // Rotating the text layer is more complicated since the divs inside the - // the text layer are rotated. - // TODO: This could probably be simplified by drawing the text layer in - // one orientation then rotating overall. - var textLayerViewport = this.textLayer.viewport; - var textRelativeRotation = this.viewport.rotation - - textLayerViewport.rotation; - var textAbsRotation = Math.abs(textRelativeRotation); - var scale = width / textLayerViewport.width; - if (textAbsRotation === 90 || textAbsRotation === 270) { - scale = width / textLayerViewport.height; - } - var textLayerDiv = this.textLayer.textLayerDiv; - var transX, transY; - switch (textAbsRotation) { - case 0: - transX = transY = 0; - break; - case 90: - transX = 0; - transY = '-' + textLayerDiv.style.height; - break; - case 180: - transX = '-' + textLayerDiv.style.width; - transY = '-' + textLayerDiv.style.height; - break; - case 270: - transX = '-' + textLayerDiv.style.width; - transY = 0; - break; - default: - console.error('Bad rotation value.'); - break; - } - CustomStyle.setProp('transform', textLayerDiv, - 'rotate(' + textAbsRotation + 'deg) ' + - 'scale(' + scale + ', ' + scale + ') ' + - 'translate(' + transX + ', ' + transY + ')'); - CustomStyle.setProp('transformOrigin', textLayerDiv, '0% 0%'); - } - - if (redrawAnnotations && this.annotationLayer) { - setupAnnotations(div, this.pdfPage, this.viewport); - } - }; - - Object.defineProperty(this, 'width', { - get: function PageView_getWidth() { - return this.viewport.width; - }, - enumerable: true - }); - - Object.defineProperty(this, 'height', { - get: function PageView_getHeight() { - return this.viewport.height; - }, - enumerable: true - }); - - var self = this; - - function setupAnnotations(pageDiv, pdfPage, viewport) { - - function bindLink(link, dest) { - link.href = linkService.getDestinationHash(dest); - link.onclick = function pageViewSetupLinksOnclick() { - if (dest) { - linkService.navigateTo(dest); - } - return false; - }; - if (dest) { - link.className = 'internalLink'; - } - } - - function bindNamedAction(link, action) { - link.href = linkService.getAnchorUrl(''); - link.onclick = function pageViewSetupNamedActionOnClick() { - linkService.executeNamedAction(action); - return false; - }; - link.className = 'internalLink'; - } - - pdfPage.getAnnotations().then(function(annotationsData) { - viewport = viewport.clone({ dontFlip: true }); - var transform = viewport.transform; - var transformStr = 'matrix(' + transform.join(',') + ')'; - var data, element, i, ii; - - if (self.annotationLayer) { - // If an annotationLayer already exists, refresh its children's - // transformation matrices - for (i = 0, ii = annotationsData.length; i < ii; i++) { - data = annotationsData[i]; - element = self.annotationLayer.querySelector( - '[data-annotation-id="' + data.id + '"]'); - if (element) { - CustomStyle.setProp('transform', element, transformStr); - } - } - // See this.reset() - self.annotationLayer.removeAttribute('hidden'); - } else { - for (i = 0, ii = annotationsData.length; i < ii; i++) { - data = annotationsData[i]; - if (!data || !data.hasHtml) { - continue; - } - - element = PDFJS.AnnotationUtils.getHtmlElement(data, - pdfPage.commonObjs); - element.setAttribute('data-annotation-id', data.id); - mozL10n.translate(element); - - var rect = data.rect; - var view = pdfPage.view; - rect = PDFJS.Util.normalizeRect([ - rect[0], - view[3] - rect[1] + view[1], - rect[2], - view[3] - rect[3] + view[1] - ]); - element.style.left = rect[0] + 'px'; - element.style.top = rect[1] + 'px'; - element.style.position = 'absolute'; - - CustomStyle.setProp('transform', element, transformStr); - var transformOriginStr = -rect[0] + 'px ' + -rect[1] + 'px'; - CustomStyle.setProp('transformOrigin', element, transformOriginStr); - - if (data.subtype === 'Link' && !data.url) { - var link = element.getElementsByTagName('a')[0]; - if (link) { - if (data.action) { - bindNamedAction(link, data.action); - } else { - bindLink(link, ('dest' in data) ? data.dest : null); - } - } - } - - if (!self.annotationLayer) { - var annotationLayerDiv = document.createElement('div'); - annotationLayerDiv.className = 'annotationLayer'; - pageDiv.appendChild(annotationLayerDiv); - self.annotationLayer = annotationLayerDiv; - } - - self.annotationLayer.appendChild(element); - } - } - }); + container.appendChild(div); } - this.getPagePoint = function pageViewGetPagePoint(x, y) { - return this.viewport.convertToPdfPoint(x, y); - }; + PDFPageView.prototype = { + setPdfPage: function PDFPageView_setPdfPage(pdfPage) { + this.pdfPage = pdfPage; + this.pdfPageRotate = pdfPage.rotate; + var totalRotation = (this.rotation + this.pdfPageRotate) % 360; + this.viewport = pdfPage.getViewport(this.scale * CSS_UNITS, + totalRotation); + this.stats = pdfPage.stats; + this.reset(); + }, - this.draw = function pageviewDraw(callback) { - var pdfPage = this.pdfPage; - - if (this.pagePdfPromise) { - return; - } - if (!pdfPage) { - var promise = this.pageSource.getPage(); - promise.then(function(pdfPage) { - delete this.pagePdfPromise; - this.setPdfPage(pdfPage); - this.draw(callback); - }.bind(this)); - this.pagePdfPromise = promise; - return; - } - - if (this.renderingState !== RenderingStates.INITIAL) { - console.error('Must be in new state before drawing'); - } - - this.renderingState = RenderingStates.RUNNING; - - var viewport = this.viewport; - // Wrap the canvas so if it has a css transform for highdpi the overflow - // will be hidden in FF. - var canvasWrapper = document.createElement('div'); - canvasWrapper.style.width = div.style.width; - canvasWrapper.style.height = div.style.height; - canvasWrapper.classList.add('canvasWrapper'); - - var canvas = document.createElement('canvas'); - canvas.id = 'page' + this.id; - canvasWrapper.appendChild(canvas); - if (this.annotationLayer) { - // annotationLayer needs to stay on top - div.insertBefore(canvasWrapper, this.annotationLayer); - } else { - div.appendChild(canvasWrapper); - } - this.canvas = canvas; - - var ctx = canvas.getContext('2d'); - var outputScale = getOutputScale(ctx); - - if (PDFJS.useOnlyCssZoom) { - var actualSizeViewport = viewport.clone({ scale: CSS_UNITS }); - // Use a scale that will make the canvas be the original intended size - // of the page. - outputScale.sx *= actualSizeViewport.width / viewport.width; - outputScale.sy *= actualSizeViewport.height / viewport.height; - outputScale.scaled = true; - } - - if (PDFJS.maxCanvasPixels > 0) { - var pixelsInViewport = viewport.width * viewport.height; - var maxScale = Math.sqrt(PDFJS.maxCanvasPixels / pixelsInViewport); - if (outputScale.sx > maxScale || outputScale.sy > maxScale) { - outputScale.sx = maxScale; - outputScale.sy = maxScale; - outputScale.scaled = true; - this.hasRestrictedScaling = true; - } else { - this.hasRestrictedScaling = false; + destroy: function PDFPageView_destroy() { + this.zoomLayer = null; + this.reset(); + if (this.pdfPage) { + this.pdfPage.destroy(); } - } + }, - canvas.width = (Math.floor(viewport.width) * outputScale.sx) | 0; - canvas.height = (Math.floor(viewport.height) * outputScale.sy) | 0; - canvas.style.width = Math.floor(viewport.width) + 'px'; - canvas.style.height = Math.floor(viewport.height) + 'px'; - // Add the viewport so it's known what it was originally drawn with. - canvas._viewport = viewport; + reset: function PDFPageView_reset(keepAnnotations) { + if (this.renderTask) { + this.renderTask.cancel(); + } + this.resume = null; + this.renderingState = RenderingStates.INITIAL; - var textLayerDiv = null; - var textLayer = null; - if (!PDFJS.disableTextLayer) { - textLayerDiv = document.createElement('div'); - textLayerDiv.className = 'textLayer'; - textLayerDiv.style.width = canvas.style.width; - textLayerDiv.style.height = canvas.style.height; + var div = this.div; + div.style.width = Math.floor(this.viewport.width) + 'px'; + div.style.height = Math.floor(this.viewport.height) + 'px'; + + var childNodes = div.childNodes; + var currentZoomLayer = this.zoomLayer || null; + var currentAnnotationNode = (keepAnnotations && this.annotationLayer && + this.annotationLayer.div) || null; + for (var i = childNodes.length - 1; i >= 0; i--) { + var node = childNodes[i]; + if (currentZoomLayer === node || currentAnnotationNode === node) { + continue; + } + div.removeChild(node); + } + div.removeAttribute('data-loaded'); + + if (keepAnnotations) { + if (this.annotationLayer) { + // Hide annotationLayer until all elements are resized + // so they are not displayed on the already-resized page + this.annotationLayer.hide(); + } + } else { + this.annotationLayer = null; + } + + if (this.canvas) { + // Zeroing the width and height causes Firefox to release graphics + // resources immediately, which can greatly reduce memory consumption. + this.canvas.width = 0; + this.canvas.height = 0; + delete this.canvas; + } + + this.loadingIconDiv = document.createElement('div'); + this.loadingIconDiv.className = 'loadingIcon'; + div.appendChild(this.loadingIconDiv); + }, + + update: function PDFPageView_update(scale, rotation) { + this.scale = scale || this.scale; + + if (typeof rotation !== 'undefined') { + this.rotation = rotation; + } + + var totalRotation = (this.rotation + this.pdfPageRotate) % 360; + this.viewport = this.viewport.clone({ + scale: this.scale * CSS_UNITS, + rotation: totalRotation + }); + + var isScalingRestricted = false; + if (this.canvas && PDFJS.maxCanvasPixels > 0) { + var ctx = this.canvas.getContext('2d'); + var outputScale = getOutputScale(ctx); + var pixelsInViewport = this.viewport.width * this.viewport.height; + var maxScale = Math.sqrt(PDFJS.maxCanvasPixels / pixelsInViewport); + if (((Math.floor(this.viewport.width) * outputScale.sx) | 0) * + ((Math.floor(this.viewport.height) * outputScale.sy) | 0) > + PDFJS.maxCanvasPixels) { + isScalingRestricted = true; + } + } + + if (this.canvas && + (PDFJS.useOnlyCssZoom || + (this.hasRestrictedScaling && isScalingRestricted))) { + this.cssTransform(this.canvas, true); + return; + } else if (this.canvas && !this.zoomLayer) { + this.zoomLayer = this.canvas.parentNode; + this.zoomLayer.style.position = 'absolute'; + } + if (this.zoomLayer) { + this.cssTransform(this.zoomLayer.firstChild); + } + this.reset(true); + }, + + /** + * Called when moved in the parent's container. + */ + updatePosition: function PDFPageView_updatePosition() { + if (this.textLayer) { + this.textLayer.render(TEXT_LAYER_RENDER_DELAY); + } + }, + + cssTransform: function PDFPageView_transform(canvas, redrawAnnotations) { + // Scale canvas, canvas wrapper, and page container. + var width = this.viewport.width; + var height = this.viewport.height; + var div = this.div; + canvas.style.width = canvas.parentNode.style.width = div.style.width = + Math.floor(width) + 'px'; + canvas.style.height = canvas.parentNode.style.height = div.style.height = + Math.floor(height) + 'px'; + // The canvas may have been originally rotated, rotate relative to that. + var relativeRotation = this.viewport.rotation - canvas._viewport.rotation; + var absRotation = Math.abs(relativeRotation); + var scaleX = 1, scaleY = 1; + if (absRotation === 90 || absRotation === 270) { + // Scale x and y because of the rotation. + scaleX = height / width; + scaleY = width / height; + } + var cssTransform = 'rotate(' + relativeRotation + 'deg) ' + + 'scale(' + scaleX + ',' + scaleY + ')'; + CustomStyle.setProp('transform', canvas, cssTransform); + + if (this.textLayer) { + // Rotating the text layer is more complicated since the divs inside the + // the text layer are rotated. + // TODO: This could probably be simplified by drawing the text layer in + // one orientation then rotating overall. + var textLayerViewport = this.textLayer.viewport; + var textRelativeRotation = this.viewport.rotation - + textLayerViewport.rotation; + var textAbsRotation = Math.abs(textRelativeRotation); + var scale = width / textLayerViewport.width; + if (textAbsRotation === 90 || textAbsRotation === 270) { + scale = width / textLayerViewport.height; + } + var textLayerDiv = this.textLayer.textLayerDiv; + var transX, transY; + switch (textAbsRotation) { + case 0: + transX = transY = 0; + break; + case 90: + transX = 0; + transY = '-' + textLayerDiv.style.height; + break; + case 180: + transX = '-' + textLayerDiv.style.width; + transY = '-' + textLayerDiv.style.height; + break; + case 270: + transX = '-' + textLayerDiv.style.width; + transY = 0; + break; + default: + console.error('Bad rotation value.'); + break; + } + CustomStyle.setProp('transform', textLayerDiv, + 'rotate(' + textAbsRotation + 'deg) ' + + 'scale(' + scale + ', ' + scale + ') ' + + 'translate(' + transX + ', ' + transY + ')'); + CustomStyle.setProp('transformOrigin', textLayerDiv, '0% 0%'); + } + + if (redrawAnnotations && this.annotationLayer) { + this.annotationLayer.setupAnnotations(this.viewport); + } + }, + + get width() { + return this.viewport.width; + }, + + get height() { + return this.viewport.height; + }, + + getPagePoint: function PDFPageView_getPagePoint(x, y) { + return this.viewport.convertToPdfPoint(x, y); + }, + + draw: function PDFPageView_draw() { + if (this.renderingState !== RenderingStates.INITIAL) { + console.error('Must be in new state before drawing'); + } + + this.renderingState = RenderingStates.RUNNING; + + var pdfPage = this.pdfPage; + var viewport = this.viewport; + var div = this.div; + // Wrap the canvas so if it has a css transform for highdpi the overflow + // will be hidden in FF. + var canvasWrapper = document.createElement('div'); + canvasWrapper.style.width = div.style.width; + canvasWrapper.style.height = div.style.height; + canvasWrapper.classList.add('canvasWrapper'); + + var canvas = document.createElement('canvas'); + canvas.id = 'page' + this.id; + canvasWrapper.appendChild(canvas); if (this.annotationLayer) { // annotationLayer needs to stay on top - div.insertBefore(textLayerDiv, this.annotationLayer); + div.insertBefore(canvasWrapper, this.annotationLayer.div); } else { - div.appendChild(textLayerDiv); + div.appendChild(canvasWrapper); + } + this.canvas = canvas; + + var ctx = canvas.getContext('2d'); + var outputScale = getOutputScale(ctx); + + if (PDFJS.useOnlyCssZoom) { + var actualSizeViewport = viewport.clone({ scale: CSS_UNITS }); + // Use a scale that will make the canvas be the original intended size + // of the page. + outputScale.sx *= actualSizeViewport.width / viewport.width; + outputScale.sy *= actualSizeViewport.height / viewport.height; + outputScale.scaled = true; } - textLayer = this.viewer.createTextLayerBuilder(textLayerDiv, this.id - 1, - this.viewport); - } - this.textLayer = textLayer; - - // TODO(mack): use data attributes to store these - ctx._scaleX = outputScale.sx; - ctx._scaleY = outputScale.sy; - if (outputScale.scaled) { - ctx.scale(outputScale.sx, outputScale.sy); - } - - // Rendering area - - var self = this; - function pageViewDrawCallback(error) { - // The renderTask may have been replaced by a new one, so only remove the - // reference to the renderTask if it matches the one that is triggering - // this callback. - if (renderTask === self.renderTask) { - self.renderTask = null; + if (PDFJS.maxCanvasPixels > 0) { + var pixelsInViewport = viewport.width * viewport.height; + var maxScale = Math.sqrt(PDFJS.maxCanvasPixels / pixelsInViewport); + if (outputScale.sx > maxScale || outputScale.sy > maxScale) { + outputScale.sx = maxScale; + outputScale.sy = maxScale; + outputScale.scaled = true; + this.hasRestrictedScaling = true; + } else { + this.hasRestrictedScaling = false; + } } - if (error === 'cancelled') { - return; + canvas.width = (Math.floor(viewport.width) * outputScale.sx) | 0; + canvas.height = (Math.floor(viewport.height) * outputScale.sy) | 0; + canvas.style.width = Math.floor(viewport.width) + 'px'; + canvas.style.height = Math.floor(viewport.height) + 'px'; + // Add the viewport so it's known what it was originally drawn with. + canvas._viewport = viewport; + + var textLayerDiv = null; + var textLayer = null; + if (this.textLayerFactory) { + textLayerDiv = document.createElement('div'); + textLayerDiv.className = 'textLayer'; + textLayerDiv.style.width = canvas.style.width; + textLayerDiv.style.height = canvas.style.height; + if (this.annotationLayer) { + // annotationLayer needs to stay on top + div.insertBefore(textLayerDiv, this.annotationLayer.div); + } else { + div.appendChild(textLayerDiv); + } + + textLayer = this.textLayerFactory.createTextLayerBuilder(textLayerDiv, + this.id - 1, + this.viewport); + } + this.textLayer = textLayer; + + // TODO(mack): use data attributes to store these + ctx._scaleX = outputScale.sx; + ctx._scaleY = outputScale.sy; + if (outputScale.scaled) { + ctx.scale(outputScale.sx, outputScale.sy); } - self.renderingState = RenderingStates.FINISHED; - - if (self.loadingIconDiv) { - div.removeChild(self.loadingIconDiv); - delete self.loadingIconDiv; - } - - if (self.zoomLayer) { - div.removeChild(self.zoomLayer); - self.zoomLayer = null; - } - - self.error = error; - self.stats = pdfPage.stats; - self.updateStats(); - if (self.onAfterDraw) { - self.onAfterDraw(); - } - - var event = document.createEvent('CustomEvent'); - event.initCustomEvent('pagerender', true, true, { - pageNumber: pdfPage.pageNumber + var resolveRenderPromise, rejectRenderPromise; + var promise = new Promise(function (resolve, reject) { + resolveRenderPromise = resolve; + rejectRenderPromise = reject; }); - div.dispatchEvent(event); - callback(); - } + // Rendering area - var renderContext = { - canvasContext: ctx, - viewport: this.viewport, - // intent: 'default', // === 'display' - continueCallback: function pdfViewcContinueCallback(cont) { - if (!self.renderingQueue.isHighestPriority(self)) { - self.renderingState = RenderingStates.PAUSED; - self.resume = function resumeCallback() { - self.renderingState = RenderingStates.RUNNING; - cont(); - }; + var self = this; + function pageViewDrawCallback(error) { + // The renderTask may have been replaced by a new one, so only remove + // the reference to the renderTask if it matches the one that is + // triggering this callback. + if (renderTask === self.renderTask) { + self.renderTask = null; + } + + if (error === 'cancelled') { + rejectRenderPromise(error); return; } - cont(); - } - }; - var renderTask = this.renderTask = this.pdfPage.render(renderContext); - this.renderTask.promise.then( - function pdfPageRenderCallback() { - pageViewDrawCallback(null); - if (textLayer) { - self.pdfPage.getTextContent().then( - function textContentResolved(textContent) { - textLayer.setTextContent(textContent); - } - ); + self.renderingState = RenderingStates.FINISHED; + + if (self.loadingIconDiv) { + div.removeChild(self.loadingIconDiv); + delete self.loadingIconDiv; + } + + if (self.zoomLayer) { + div.removeChild(self.zoomLayer); + self.zoomLayer = null; + } + + self.error = error; + self.stats = pdfPage.stats; + if (self.onAfterDraw) { + self.onAfterDraw(); + } + var event = document.createEvent('CustomEvent'); + event.initCustomEvent('pagerendered', true, true, { + pageNumber: self.id + }); + div.dispatchEvent(event); + + if (!error) { + resolveRenderPromise(undefined); + } else { + rejectRenderPromise(error); } - }, - function pdfPageRenderError(error) { - pageViewDrawCallback(error); } - ); - setupAnnotations(div, pdfPage, this.viewport); - div.setAttribute('data-loaded', true); - - // Add the page to the cache at the start of drawing. That way it can be - // evicted from the cache and destroyed even if we pause its rendering. - cache.push(this); - }; - - this.beforePrint = function pageViewBeforePrint() { - var pdfPage = this.pdfPage; - - var viewport = pdfPage.getViewport(1); - // Use the same hack we use for high dpi displays for printing to get better - // output until bug 811002 is fixed in FF. - var PRINT_OUTPUT_SCALE = 2; - var canvas = document.createElement('canvas'); - canvas.width = Math.floor(viewport.width) * PRINT_OUTPUT_SCALE; - canvas.height = Math.floor(viewport.height) * PRINT_OUTPUT_SCALE; - canvas.style.width = (PRINT_OUTPUT_SCALE * viewport.width) + 'pt'; - canvas.style.height = (PRINT_OUTPUT_SCALE * viewport.height) + 'pt'; - var cssScale = 'scale(' + (1 / PRINT_OUTPUT_SCALE) + ', ' + - (1 / PRINT_OUTPUT_SCALE) + ')'; - CustomStyle.setProp('transform' , canvas, cssScale); - CustomStyle.setProp('transformOrigin' , canvas, '0% 0%'); - - var printContainer = document.getElementById('printContainer'); - var canvasWrapper = document.createElement('div'); - canvasWrapper.style.width = viewport.width + 'pt'; - canvasWrapper.style.height = viewport.height + 'pt'; - canvasWrapper.appendChild(canvas); - printContainer.appendChild(canvasWrapper); - - canvas.mozPrintCallback = function(obj) { - var ctx = obj.context; - - ctx.save(); - ctx.fillStyle = 'rgb(255, 255, 255)'; - ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.restore(); - ctx.scale(PRINT_OUTPUT_SCALE, PRINT_OUTPUT_SCALE); + var renderContinueCallback = null; + if (this.renderingQueue) { + renderContinueCallback = function renderContinueCallback(cont) { + if (!self.renderingQueue.isHighestPriority(self)) { + self.renderingState = RenderingStates.PAUSED; + self.resume = function resumeCallback() { + self.renderingState = RenderingStates.RUNNING; + cont(); + }; + return; + } + cont(); + }; + } var renderContext = { canvasContext: ctx, - viewport: viewport, - intent: 'print' + viewport: this.viewport, + // intent: 'default', // === 'display' + continueCallback: renderContinueCallback }; + var renderTask = this.renderTask = this.pdfPage.render(renderContext); - pdfPage.render(renderContext).promise.then(function() { - // Tell the printEngine that rendering this canvas/page has finished. - obj.done(); - }, function(error) { - console.error(error); - // Tell the printEngine that rendering this canvas/page has failed. - // This will make the print proces stop. - if ('abort' in obj) { - obj.abort(); - } else { - obj.done(); + this.renderTask.promise.then( + function pdfPageRenderCallback() { + pageViewDrawCallback(null); + if (textLayer) { + self.pdfPage.getTextContent().then( + function textContentResolved(textContent) { + textLayer.setTextContent(textContent); + textLayer.render(TEXT_LAYER_RENDER_DELAY); + } + ); + } + }, + function pdfPageRenderError(error) { + pageViewDrawCallback(error); } - }); - }; + ); + + if (this.annotationsLayerFactory) { + if (!this.annotationLayer) { + this.annotationLayer = this.annotationsLayerFactory. + createAnnotationsLayerBuilder(div, this.pdfPage); + } + this.annotationLayer.setupAnnotations(this.viewport); + } + div.setAttribute('data-loaded', true); + + if (self.onBeforeDraw) { + self.onBeforeDraw(); + } + return promise; + }, + + beforePrint: function PDFPageView_beforePrint() { + var pdfPage = this.pdfPage; + + var viewport = pdfPage.getViewport(1); + // Use the same hack we use for high dpi displays for printing to get + // better output until bug 811002 is fixed in FF. + var PRINT_OUTPUT_SCALE = 2; + var canvas = document.createElement('canvas'); + canvas.width = Math.floor(viewport.width) * PRINT_OUTPUT_SCALE; + canvas.height = Math.floor(viewport.height) * PRINT_OUTPUT_SCALE; + canvas.style.width = (PRINT_OUTPUT_SCALE * viewport.width) + 'pt'; + canvas.style.height = (PRINT_OUTPUT_SCALE * viewport.height) + 'pt'; + var cssScale = 'scale(' + (1 / PRINT_OUTPUT_SCALE) + ', ' + + (1 / PRINT_OUTPUT_SCALE) + ')'; + CustomStyle.setProp('transform' , canvas, cssScale); + CustomStyle.setProp('transformOrigin' , canvas, '0% 0%'); + + var printContainer = document.getElementById('printContainer'); + var canvasWrapper = document.createElement('div'); + canvasWrapper.style.width = viewport.width + 'pt'; + canvasWrapper.style.height = viewport.height + 'pt'; + canvasWrapper.appendChild(canvas); + printContainer.appendChild(canvasWrapper); + + canvas.mozPrintCallback = function(obj) { + var ctx = obj.context; + + ctx.save(); + ctx.fillStyle = 'rgb(255, 255, 255)'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.restore(); + ctx.scale(PRINT_OUTPUT_SCALE, PRINT_OUTPUT_SCALE); + + var renderContext = { + canvasContext: ctx, + viewport: viewport, + intent: 'print' + }; + + pdfPage.render(renderContext).promise.then(function() { + // Tell the printEngine that rendering this canvas/page has finished. + obj.done(); + }, function(error) { + console.error(error); + // Tell the printEngine that rendering this canvas/page has failed. + // This will make the print proces stop. + if ('abort' in obj) { + obj.abort(); + } else { + obj.done(); + } + }); + }; + }, }; - this.updateStats = function pageViewUpdateStats() { - if (!this.stats) { - return; - } - - if (PDFJS.pdfBug && Stats.enabled) { - var stats = this.stats; - Stats.add(this.id, stats); - } - }; -}; + return PDFPageView; +})(); -var FIND_SCROLL_OFFSET_TOP = -50; -var FIND_SCROLL_OFFSET_LEFT = -400; var MAX_TEXT_DIVS_TO_RENDER = 100000; -var RENDER_DELAY = 200; // ms var NonWhitespaceRegexp = /\S/; @@ -3440,9 +3369,6 @@ function isAllWhitespace(str) { * @property {HTMLDivElement} textLayerDiv - The text layer container. * @property {number} pageIndex - The page index. * @property {PageViewport} viewport - The viewport of the text layer. - * @property {ILastScrollSource} lastScrollSource - The object that records when - * last time scroll happened. - * @property {boolean} isViewerInPresentationMode * @property {PDFFindController} findController */ @@ -3456,18 +3382,27 @@ function isAllWhitespace(str) { var TextLayerBuilder = (function TextLayerBuilderClosure() { function TextLayerBuilder(options) { this.textLayerDiv = options.textLayerDiv; - this.layoutDone = false; + this.renderingDone = false; this.divContentDone = false; this.pageIdx = options.pageIndex; + this.pageNumber = this.pageIdx + 1; this.matches = []; - this.lastScrollSource = options.lastScrollSource || null; this.viewport = options.viewport; - this.isViewerInPresentationMode = options.isViewerInPresentationMode; this.textDivs = []; this.findController = options.findController || null; } TextLayerBuilder.prototype = { + _finishRendering: function TextLayerBuilder_finishRendering() { + this.renderingDone = true; + + var event = document.createEvent('CustomEvent'); + event.initCustomEvent('textlayerrendered', true, true, { + pageNumber: this.pageNumber + }); + this.textLayerDiv.dispatchEvent(event); + }, + renderLayer: function TextLayerBuilder_renderLayer() { var textLayerFrag = document.createDocumentFragment(); var textDivs = this.textDivs; @@ -3478,6 +3413,7 @@ var TextLayerBuilder = (function TextLayerBuilderClosure() { // No point in rendering many divs as it would make the browser // unusable even after the divs are rendered. if (textDivsLength > MAX_TEXT_DIVS_TO_RENDER) { + this._finishRendering(); return; } @@ -3521,27 +3457,33 @@ var TextLayerBuilder = (function TextLayerBuilderClosure() { } this.textLayerDiv.appendChild(textLayerFrag); - this.renderingDone = true; + this._finishRendering(); this.updateMatches(); }, - setupRenderLayoutTimer: - function TextLayerBuilder_setupRenderLayoutTimer() { - // Schedule renderLayout() if the user has been scrolling, - // otherwise run it right away. - var self = this; - var lastScroll = (this.lastScrollSource === null ? - 0 : this.lastScrollSource.lastScroll); + /** + * Renders the text layer. + * @param {number} timeout (optional) if specified, the rendering waits + * for specified amount of ms. + */ + render: function TextLayerBuilder_render(timeout) { + if (!this.divContentDone || this.renderingDone) { + return; + } - if (Date.now() - lastScroll > RENDER_DELAY) { // Render right away + if (this.renderTimer) { + clearTimeout(this.renderTimer); + this.renderTimer = null; + } + + if (!timeout) { // Render right away this.renderLayer(); } else { // Schedule - if (this.renderTimer) { - clearTimeout(this.renderTimer); - } + var self = this; this.renderTimer = setTimeout(function() { - self.setupRenderLayoutTimer(); - }, RENDER_DELAY); + self.renderLayer(); + self.renderTimer = null; + }, timeout); } }, @@ -3611,7 +3553,6 @@ var TextLayerBuilder = (function TextLayerBuilderClosure() { this.appendText(textItems[i], textContent.styles); } this.divContentDone = true; - this.setupRenderLayoutTimer(); }, convertMatches: function TextLayerBuilder_convertMatches(matches) { @@ -3673,8 +3614,9 @@ var TextLayerBuilder = (function TextLayerBuilderClosure() { var bidiTexts = this.textContent.items; var textDivs = this.textDivs; var prevEnd = null; + var pageIdx = this.pageIdx; var isSelectedPage = (this.findController === null ? - false : (this.pageIdx === this.findController.selected.pageIdx)); + false : (pageIdx === this.findController.selected.pageIdx)); var selectedMatchIdx = (this.findController === null ? -1 : this.findController.selected.matchIdx); var highlightAll = (this.findController === null ? @@ -3720,10 +3662,9 @@ var TextLayerBuilder = (function TextLayerBuilderClosure() { var isSelected = (isSelectedPage && i === selectedMatchIdx); var highlightSuffix = (isSelected ? ' selected' : ''); - if (isSelected && !this.isViewerInPresentationMode) { - scrollIntoView(textDivs[begin.divIdx], - { top: FIND_SCROLL_OFFSET_TOP, - left: FIND_SCROLL_OFFSET_LEFT }); + if (this.findController) { + this.findController.updateMatchPosition(pageIdx, i, textDivs, + begin.divIdx, end.divIdx); } // Match inside new div. @@ -3795,6 +3736,186 @@ var TextLayerBuilder = (function TextLayerBuilderClosure() { return TextLayerBuilder; })(); +/** + * @constructor + * @implements IPDFTextLayerFactory + */ +function DefaultTextLayerFactory() {} +DefaultTextLayerFactory.prototype = { + /** + * @param {HTMLDivElement} textLayerDiv + * @param {number} pageIndex + * @param {PageViewport} viewport + * @returns {TextLayerBuilder} + */ + createTextLayerBuilder: function (textLayerDiv, pageIndex, viewport) { + return new TextLayerBuilder({ + textLayerDiv: textLayerDiv, + pageIndex: pageIndex, + viewport: viewport + }); + } +}; + + +/** + * @typedef {Object} AnnotationsLayerBuilderOptions + * @property {HTMLDivElement} pageDiv + * @property {PDFPage} pdfPage + * @property {IPDFLinkService} linkService + */ + +/** + * @class + */ +var AnnotationsLayerBuilder = (function AnnotationsLayerBuilderClosure() { + /** + * @param {AnnotationsLayerBuilderOptions} options + * @constructs AnnotationsLayerBuilder + */ + function AnnotationsLayerBuilder(options) { + this.pageDiv = options.pageDiv; + this.pdfPage = options.pdfPage; + this.linkService = options.linkService; + + this.div = null; + } + AnnotationsLayerBuilder.prototype = + /** @lends AnnotationsLayerBuilder.prototype */ { + + /** + * @param {PageViewport} viewport + */ + setupAnnotations: + function AnnotationsLayerBuilder_setupAnnotations(viewport) { + function bindLink(link, dest) { + link.href = linkService.getDestinationHash(dest); + link.onclick = function annotationsLayerBuilderLinksOnclick() { + if (dest) { + linkService.navigateTo(dest); + } + return false; + }; + if (dest) { + link.className = 'internalLink'; + } + } + + function bindNamedAction(link, action) { + link.href = linkService.getAnchorUrl(''); + link.onclick = function annotationsLayerBuilderNamedActionOnClick() { + linkService.executeNamedAction(action); + return false; + }; + link.className = 'internalLink'; + } + + var linkService = this.linkService; + var pdfPage = this.pdfPage; + var self = this; + + pdfPage.getAnnotations().then(function (annotationsData) { + viewport = viewport.clone({ dontFlip: true }); + var transform = viewport.transform; + var transformStr = 'matrix(' + transform.join(',') + ')'; + var data, element, i, ii; + + if (self.div) { + // If an annotationLayer already exists, refresh its children's + // transformation matrices + for (i = 0, ii = annotationsData.length; i < ii; i++) { + data = annotationsData[i]; + element = self.div.querySelector( + '[data-annotation-id="' + data.id + '"]'); + if (element) { + CustomStyle.setProp('transform', element, transformStr); + } + } + // See PDFPageView.reset() + self.div.removeAttribute('hidden'); + } else { + for (i = 0, ii = annotationsData.length; i < ii; i++) { + data = annotationsData[i]; + if (!data || !data.hasHtml) { + continue; + } + + element = PDFJS.AnnotationUtils.getHtmlElement(data, + pdfPage.commonObjs); + element.setAttribute('data-annotation-id', data.id); + if (typeof mozL10n !== 'undefined') { + mozL10n.translate(element); + } + + var rect = data.rect; + var view = pdfPage.view; + rect = PDFJS.Util.normalizeRect([ + rect[0], + view[3] - rect[1] + view[1], + rect[2], + view[3] - rect[3] + view[1] + ]); + element.style.left = rect[0] + 'px'; + element.style.top = rect[1] + 'px'; + element.style.position = 'absolute'; + + CustomStyle.setProp('transform', element, transformStr); + var transformOriginStr = -rect[0] + 'px ' + -rect[1] + 'px'; + CustomStyle.setProp('transformOrigin', element, transformOriginStr); + + if (data.subtype === 'Link' && !data.url) { + var link = element.getElementsByTagName('a')[0]; + if (link) { + if (data.action) { + bindNamedAction(link, data.action); + } else { + bindLink(link, ('dest' in data) ? data.dest : null); + } + } + } + + if (!self.div) { + var annotationLayerDiv = document.createElement('div'); + annotationLayerDiv.className = 'annotationLayer'; + self.pageDiv.appendChild(annotationLayerDiv); + self.div = annotationLayerDiv; + } + + self.div.appendChild(element); + } + } + }); + }, + + hide: function () { + if (!this.div) { + return; + } + this.div.setAttribute('hidden', 'true'); + } + }; + return AnnotationsLayerBuilder; +})(); + +/** + * @constructor + * @implements IPDFAnnotationsLayerFactory + */ +function DefaultAnnotationsLayerFactory() {} +DefaultAnnotationsLayerFactory.prototype = { + /** + * @param {HTMLDivElement} pageDiv + * @param {PDFPage} pdfPage + * @returns {AnnotationsLayerBuilder} + */ + createAnnotationsLayerBuilder: function (pageDiv, pdfPage) { + return new AnnotationsLayerBuilder({ + pageDiv: pageDiv, + pdfPage: pdfPage + }); + } +}; + /** * @typedef {Object} PDFViewerOptions @@ -3808,10 +3929,29 @@ var TextLayerBuilder = (function TextLayerBuilderClosure() { /** * Simple viewer control to display PDF content/pages. * @class - * @implements {ILastScrollSource} * @implements {IRenderableView} */ var PDFViewer = (function pdfViewer() { + function PDFPageViewBuffer(size) { + var data = []; + this.push = function cachePush(view) { + var i = data.indexOf(view); + if (i >= 0) { + data.splice(i, 1); + } + data.push(view); + if (data.length > size) { + data.shift().destroy(); + } + }; + this.resize = function (newSize) { + size = newSize; + while (data.length > size) { + data.shift().destroy(); + } + }; + } + /** * @constructs PDFViewer * @param {PDFViewerOptions} options @@ -3831,7 +3971,6 @@ var PDFViewer = (function pdfViewer() { } this.scroll = watchScroll(this.container, this._scrollUpdate.bind(this)); - this.lastScroll = 0; this.updateInProgress = false; this.presentationModeState = PresentationModeState.UNKNOWN; this._resetView(); @@ -3867,7 +4006,6 @@ var PDFViewer = (function pdfViewer() { return; } - this.pages[val - 1].updateStats(); event.previousPageNumber = this._currentPageNumber; this._currentPageNumber = val; event.pageNumber = val; @@ -3973,18 +4111,19 @@ var PDFViewer = (function pdfViewer() { }); this.onePageRendered = onePageRendered; - var bindOnAfterDraw = function (pageView) { + var bindOnAfterAndBeforeDraw = function (pageView) { + pageView.onBeforeDraw = function pdfViewLoadOnBeforeDraw() { + // Add the page to the buffer at the start of drawing. That way it can + // be evicted from the buffer and destroyed even if we pause its + // rendering. + self._buffer.push(this); + }; // when page is painted, using the image as thumbnail base pageView.onAfterDraw = function pdfViewLoadOnAfterDraw() { if (!isOnePageRenderedResolved) { isOnePageRenderedResolved = true; resolveOnePageRendered(); } - var event = document.createEvent('CustomEvent'); - event.initCustomEvent('pagerendered', true, true, { - pageNumber: pageView.id - }); - self.container.dispatchEvent(event); }; }; @@ -3997,12 +4136,20 @@ var PDFViewer = (function pdfViewer() { var scale = this._currentScale || 1.0; var viewport = pdfPage.getViewport(scale * CSS_UNITS); for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) { - var pageSource = new PDFPageSource(pdfDocument, pageNum); - var pageView = new PageView(this.viewer, pageNum, scale, - viewport.clone(), this.linkService, - this.renderingQueue, this.cache, - pageSource, this); - bindOnAfterDraw(pageView); + var textLayerFactory = null; + if (!PDFJS.disableTextLayer) { + textLayerFactory = this; + } + var pageView = new PDFPageView({ + container: this.viewer, + id: pageNum, + scale: scale, + defaultViewport: viewport.clone(), + renderingQueue: this.renderingQueue, + textLayerFactory: textLayerFactory, + annotationsLayerFactory: this + }); + bindOnAfterAndBeforeDraw(pageView); this.pages.push(pageView); } @@ -4043,13 +4190,14 @@ var PDFViewer = (function pdfViewer() { }, _resetView: function () { - this.cache = new Cache(DEFAULT_CACHE_SIZE); this.pages = []; this._currentPageNumber = 1; this._currentScale = UNKNOWN_SCALE; this._currentScaleValue = null; + this._buffer = new PDFPageViewBuffer(DEFAULT_CACHE_SIZE); this.location = null; this._pagesRotation = 0; + this._pagesRequests = []; var container = this.viewer; while (container.hasChildNodes()) { @@ -4058,12 +4206,13 @@ var PDFViewer = (function pdfViewer() { }, _scrollUpdate: function () { - this.lastScroll = Date.now(); - if (this.pagesCount === 0) { return; } this.update(); + for (var i = 0, ii = this.pages.length; i < ii; i++) { + this.pages[i].updatePosition(); + } }, _setScaleUpdatePages: function pdfViewer_setScaleUpdatePages( @@ -4160,7 +4309,6 @@ var PDFViewer = (function pdfViewer() { scrollPageIntoView: function PDFViewer_scrollPageIntoView(pageNumber, dest) { var pageView = this.pages[pageNumber - 1]; - var pageViewDiv = pageView.el; if (this.presentationModeState === PresentationModeState.FULLSCREEN) { @@ -4174,7 +4322,7 @@ var PDFViewer = (function pdfViewer() { this._setScale(this.currentScaleValue, true); } if (!dest) { - scrollIntoView(pageViewDiv); + scrollIntoView(pageView.div); return; } @@ -4237,7 +4385,7 @@ var PDFViewer = (function pdfViewer() { } if (scale === 'page-fit' && !dest[4]) { - scrollIntoView(pageViewDiv); + scrollIntoView(pageView.div); return; } @@ -4248,7 +4396,7 @@ var PDFViewer = (function pdfViewer() { var left = Math.min(boundingRect[0][0], boundingRect[1][0]); var top = Math.min(boundingRect[0][1], boundingRect[1][1]); - scrollIntoView(pageViewDiv, { left: left, top: top }); + scrollIntoView(pageView.div, { left: left, top: top }); }, _updateLocation: function (firstPage) { @@ -4290,7 +4438,7 @@ var PDFViewer = (function pdfViewer() { var suggestedCacheSize = Math.max(DEFAULT_CACHE_SIZE, 2 * visiblePages.length + 1); - this.cache.resize(suggestedCacheSize); + this._buffer.resize(suggestedCacheSize); this.renderingQueue.renderHighestPriority(visible); @@ -4366,13 +4514,38 @@ var PDFViewer = (function pdfViewer() { } }, + /** + * @param {PDFPageView} pageView + * @returns {PDFPage} + * @private + */ + _ensurePdfPageLoaded: function (pageView) { + if (pageView.pdfPage) { + return Promise.resolve(pageView.pdfPage); + } + var pageNumber = pageView.id; + if (this._pagesRequests[pageNumber]) { + return this._pagesRequests[pageNumber]; + } + var promise = this.pdfDocument.getPage(pageNumber).then( + function (pdfPage) { + pageView.setPdfPage(pdfPage); + this._pagesRequests[pageNumber] = null; + return pdfPage; + }.bind(this)); + this._pagesRequests[pageNumber] = promise; + return promise; + }, + forceRendering: function (currentlyVisiblePages) { var visiblePages = currentlyVisiblePages || this._getVisiblePages(); var pageView = this.renderingQueue.getHighestPriority(visiblePages, this.pages, this.scroll.down); if (pageView) { - this.renderingQueue.renderView(pageView); + this._ensurePdfPageLoaded(pageView).then(function () { + this.renderingQueue.renderView(pageView); + }.bind(this)); return true; } return false; @@ -4385,9 +4558,9 @@ var PDFViewer = (function pdfViewer() { }, /** - * @param textLayerDiv {HTMLDivElement} - * @param pageIndex {number} - * @param viewport {PageViewport} + * @param {HTMLDivElement} textLayerDiv + * @param {number} pageIndex + * @param {PageViewport} viewport * @returns {TextLayerBuilder} */ createTextLayerBuilder: function (textLayerDiv, pageIndex, viewport) { @@ -4397,9 +4570,20 @@ var PDFViewer = (function pdfViewer() { textLayerDiv: textLayerDiv, pageIndex: pageIndex, viewport: viewport, - lastScrollSource: this, - isViewerInPresentationMode: isViewerInPresentationMode, - findController: this.findController + findController: isViewerInPresentationMode ? null : this.findController + }); + }, + + /** + * @param {HTMLDivElement} pageDiv + * @param {PDFPage} pdfPage + * @returns {AnnotationsLayerBuilder} + */ + createAnnotationsLayerBuilder: function (pageDiv, pdfPage) { + return new AnnotationsLayerBuilder({ + pageDiv: pageDiv, + pdfPage: pdfPage, + linkService: this.linkService }); }, @@ -4458,32 +4642,632 @@ var SimpleLinkService = (function SimpleLinkServiceClosure() { return SimpleLinkService; })(); + +var THUMBNAIL_SCROLL_MARGIN = -19; + + +var THUMBNAIL_WIDTH = 98; // px +var THUMBNAIL_CANVAS_BORDER_WIDTH = 1; // px + /** - * PDFPage object source. - * @class + * @typedef {Object} PDFThumbnailViewOptions + * @property {HTMLDivElement} container - The viewer element. + * @property {number} id - The thumbnail's unique ID (normally its number). + * @property {PageViewport} defaultViewport - The page viewport. + * @property {IPDFLinkService} linkService - The navigation/linking service. + * @property {PDFRenderingQueue} renderingQueue - The rendering queue object. */ -var PDFPageSource = (function PDFPageSourceClosure() { - /** - * @constructs - * @param {PDFDocument} pdfDocument - * @param {number} pageNumber - * @constructor - */ - function PDFPageSource(pdfDocument, pageNumber) { - this.pdfDocument = pdfDocument; - this.pageNumber = pageNumber; + +/** + * @class + * @implements {IRenderableView} + */ +var PDFThumbnailView = (function PDFThumbnailViewClosure() { + function getTempCanvas(width, height) { + var tempCanvas = PDFThumbnailView.tempImageCache; + if (!tempCanvas) { + tempCanvas = document.createElement('canvas'); + PDFThumbnailView.tempImageCache = tempCanvas; + } + tempCanvas.width = width; + tempCanvas.height = height; + + // Since this is a temporary canvas, we need to fill the canvas with a white + // background ourselves. |_getPageDrawContext| uses CSS rules for this. + var ctx = tempCanvas.getContext('2d'); + ctx.save(); + ctx.fillStyle = 'rgb(255, 255, 255)'; + ctx.fillRect(0, 0, width, height); + ctx.restore(); + return tempCanvas; } - PDFPageSource.prototype = /** @lends PDFPageSource.prototype */ { + /** + * @constructs PDFThumbnailView + * @param {PDFThumbnailViewOptions} options + */ + function PDFThumbnailView(options) { + var container = options.container; + var id = options.id; + var defaultViewport = options.defaultViewport; + var linkService = options.linkService; + var renderingQueue = options.renderingQueue; + + this.id = id; + this.renderingId = 'thumbnail' + id; + + this.pdfPage = null; + this.rotation = 0; + this.viewport = defaultViewport; + this.pdfPageRotate = defaultViewport.rotation; + + this.linkService = linkService; + this.renderingQueue = renderingQueue; + + this.hasImage = false; + this.resume = null; + this.renderingState = RenderingStates.INITIAL; + + this.pageWidth = this.viewport.width; + this.pageHeight = this.viewport.height; + this.pageRatio = this.pageWidth / this.pageHeight; + + this.canvasWidth = THUMBNAIL_WIDTH; + this.canvasHeight = (this.canvasWidth / this.pageRatio) | 0; + this.scale = this.canvasWidth / this.pageWidth; + + var anchor = document.createElement('a'); + anchor.href = linkService.getAnchorUrl('#page=' + id); + anchor.title = mozL10n.get('thumb_page_title', {page: id}, 'Page {{page}}'); + anchor.onclick = function stopNavigation() { + linkService.page = id; + return false; + }; + + var div = document.createElement('div'); + div.id = 'thumbnailContainer' + id; + div.className = 'thumbnail'; + this.el = div; // TODO: replace 'el' property usage. + this.div = div; + + if (id === 1) { + // Highlight the thumbnail of the first page when no page number is + // specified (or exists in cache) when the document is loaded. + div.classList.add('selected'); + } + + var ring = document.createElement('div'); + ring.className = 'thumbnailSelectionRing'; + var borderAdjustment = 2 * THUMBNAIL_CANVAS_BORDER_WIDTH; + ring.style.width = this.canvasWidth + borderAdjustment + 'px'; + ring.style.height = this.canvasHeight + borderAdjustment + 'px'; + this.ring = ring; + + div.appendChild(ring); + anchor.appendChild(div); + container.appendChild(anchor); + } + + PDFThumbnailView.prototype = { + setPdfPage: function PDFThumbnailView_setPdfPage(pdfPage) { + this.pdfPage = pdfPage; + this.pdfPageRotate = pdfPage.rotate; + var totalRotation = (this.rotation + this.pdfPageRotate) % 360; + this.viewport = pdfPage.getViewport(1, totalRotation); + this.reset(); + }, + + reset: function PDFThumbnailView_reset() { + if (this.renderTask) { + this.renderTask.cancel(); + } + this.hasImage = false; + this.resume = null; + this.renderingState = RenderingStates.INITIAL; + + this.pageWidth = this.viewport.width; + this.pageHeight = this.viewport.height; + this.pageRatio = this.pageWidth / this.pageHeight; + + this.canvasHeight = (this.canvasWidth / this.pageRatio) | 0; + this.scale = (this.canvasWidth / this.pageWidth); + + this.div.removeAttribute('data-loaded'); + var ring = this.ring; + var childNodes = ring.childNodes; + for (var i = childNodes.length - 1; i >= 0; i--) { + ring.removeChild(childNodes[i]); + } + var borderAdjustment = 2 * THUMBNAIL_CANVAS_BORDER_WIDTH; + ring.style.width = this.canvasWidth + borderAdjustment + 'px'; + ring.style.height = this.canvasHeight + borderAdjustment + 'px'; + + if (this.canvas) { + // Zeroing the width and height causes Firefox to release graphics + // resources immediately, which can greatly reduce memory consumption. + this.canvas.width = 0; + this.canvas.height = 0; + delete this.canvas; + } + }, + + update: function PDFThumbnailView_update(rotation) { + if (typeof rotation !== 'undefined') { + this.rotation = rotation; + } + var totalRotation = (this.rotation + this.pdfPageRotate) % 360; + this.viewport = this.viewport.clone({ + scale: 1, + rotation: totalRotation + }); + this.reset(); + }, + /** - * @returns {Promise} + * @private */ - getPage: function () { - return this.pdfDocument.getPage(this.pageNumber); + _getPageDrawContext: function PDFThumbnailView_getPageDrawContext() { + var canvas = document.createElement('canvas'); + canvas.id = this.renderingId; + + canvas.width = this.canvasWidth; + canvas.height = this.canvasHeight; + canvas.className = 'thumbnailImage'; + canvas.setAttribute('aria-label', mozL10n.get('thumb_page_canvas', + {page: this.id}, 'Thumbnail of Page {{page}}')); + + this.canvas = canvas; + this.div.setAttribute('data-loaded', true); + this.ring.appendChild(canvas); + + return canvas.getContext('2d'); + }, + + draw: function PDFThumbnailView_draw() { + if (this.renderingState !== RenderingStates.INITIAL) { + console.error('Must be in new state before drawing'); + } + if (this.hasImage) { + return Promise.resolve(undefined); + } + this.hasImage = true; + this.renderingState = RenderingStates.RUNNING; + + var resolveRenderPromise, rejectRenderPromise; + var promise = new Promise(function (resolve, reject) { + resolveRenderPromise = resolve; + rejectRenderPromise = reject; + }); + + var self = this; + function thumbnailDrawCallback(error) { + // The renderTask may have been replaced by a new one, so only remove + // the reference to the renderTask if it matches the one that is + // triggering this callback. + if (renderTask === self.renderTask) { + self.renderTask = null; + } + if (error === 'cancelled') { + rejectRenderPromise(error); + return; + } + self.renderingState = RenderingStates.FINISHED; + + if (!error) { + resolveRenderPromise(undefined); + } else { + rejectRenderPromise(error); + } + } + + var ctx = this._getPageDrawContext(); + var drawViewport = this.viewport.clone({ scale: this.scale }); + var renderContinueCallback = function renderContinueCallback(cont) { + if (!self.renderingQueue.isHighestPriority(self)) { + self.renderingState = RenderingStates.PAUSED; + self.resume = function resumeCallback() { + self.renderingState = RenderingStates.RUNNING; + cont(); + }; + return; + } + cont(); + }; + + var renderContext = { + canvasContext: ctx, + viewport: drawViewport, + continueCallback: renderContinueCallback + }; + var renderTask = this.renderTask = this.pdfPage.render(renderContext); + + renderTask.promise.then( + function pdfPageRenderCallback() { + thumbnailDrawCallback(null); + }, + function pdfPageRenderError(error) { + thumbnailDrawCallback(error); + } + ); + return promise; + }, + + setImage: function PDFThumbnailView_setImage(pageView) { + var img = pageView.canvas; + if (this.hasImage || !img) { + return; + } + if (!this.pdfPage) { + this.setPdfPage(pageView.pdfPage); + } + this.hasImage = true; + this.renderingState = RenderingStates.FINISHED; + + var ctx = this._getPageDrawContext(); + var canvas = ctx.canvas; + + if (img.width <= 2 * canvas.width) { + ctx.drawImage(img, 0, 0, img.width, img.height, + 0, 0, canvas.width, canvas.height); + return; + } + // drawImage does an awful job of rescaling the image, doing it gradually. + var MAX_NUM_SCALING_STEPS = 3; + var reducedWidth = canvas.width << MAX_NUM_SCALING_STEPS; + var reducedHeight = canvas.height << MAX_NUM_SCALING_STEPS; + var reducedImage = getTempCanvas(reducedWidth, reducedHeight); + var reducedImageCtx = reducedImage.getContext('2d'); + + while (reducedWidth > img.width || reducedHeight > img.height) { + reducedWidth >>= 1; + reducedHeight >>= 1; + } + reducedImageCtx.drawImage(img, 0, 0, img.width, img.height, + 0, 0, reducedWidth, reducedHeight); + while (reducedWidth > 2 * canvas.width) { + reducedImageCtx.drawImage(reducedImage, + 0, 0, reducedWidth, reducedHeight, + 0, 0, reducedWidth >> 1, reducedHeight >> 1); + reducedWidth >>= 1; + reducedHeight >>= 1; + } + ctx.drawImage(reducedImage, 0, 0, reducedWidth, reducedHeight, + 0, 0, canvas.width, canvas.height); } }; - return PDFPageSource; + return PDFThumbnailView; +})(); + +PDFThumbnailView.tempImageCache = null; + + +/** + * @typedef {Object} PDFThumbnailViewerOptions + * @property {HTMLDivElement} container - The container for the thumbnail + * elements. + * @property {IPDFLinkService} linkService - The navigation/linking service. + * @property {PDFRenderingQueue} renderingQueue - The rendering queue object. + */ + +/** + * Simple viewer control to display thumbnails for pages. + * @class + * @implements {IRenderableView} + */ +var PDFThumbnailViewer = (function PDFThumbnailViewerClosure() { + /** + * @constructs PDFThumbnailViewer + * @param {PDFThumbnailViewerOptions} options + */ + function PDFThumbnailViewer(options) { + this.container = options.container; + this.renderingQueue = options.renderingQueue; + this.linkService = options.linkService; + + this.scroll = watchScroll(this.container, this._scrollUpdated.bind(this)); + this._resetView(); + } + + PDFThumbnailViewer.prototype = { + /** + * @private + */ + _scrollUpdated: function PDFThumbnailViewer_scrollUpdated() { + this.renderingQueue.renderHighestPriority(); + }, + + getThumbnail: function PDFThumbnailViewer_getThumbnail(index) { + return this.thumbnails[index]; + }, + + /** + * @private + */ + _getVisibleThumbs: function PDFThumbnailViewer_getVisibleThumbs() { + return getVisibleElements(this.container, this.thumbnails); + }, + + scrollThumbnailIntoView: + function PDFThumbnailViewer_scrollThumbnailIntoView(page) { + var selected = document.querySelector('.thumbnail.selected'); + if (selected) { + selected.classList.remove('selected'); + } + var thumbnail = document.getElementById('thumbnailContainer' + page); + thumbnail.classList.add('selected'); + var visibleThumbs = this._getVisibleThumbs(); + var numVisibleThumbs = visibleThumbs.views.length; + + // If the thumbnail isn't currently visible, scroll it into view. + if (numVisibleThumbs > 0) { + var first = visibleThumbs.first.id; + // Account for only one thumbnail being visible. + var last = (numVisibleThumbs > 1 ? visibleThumbs.last.id : first); + if (page <= first || page >= last) { + scrollIntoView(thumbnail, { top: THUMBNAIL_SCROLL_MARGIN }); + } + } + }, + + get pagesRotation() { + return this._pagesRotation; + }, + + set pagesRotation(rotation) { + this._pagesRotation = rotation; + for (var i = 0, l = this.thumbnails.length; i < l; i++) { + var thumb = this.thumbnails[i]; + thumb.update(rotation); + } + }, + + cleanup: function PDFThumbnailViewer_cleanup() { + var tempCanvas = PDFThumbnailView.tempImageCache; + if (tempCanvas) { + // Zeroing the width and height causes Firefox to release graphics + // resources immediately, which can greatly reduce memory consumption. + tempCanvas.width = 0; + tempCanvas.height = 0; + } + PDFThumbnailView.tempImageCache = null; + }, + + /** + * @private + */ + _resetView: function PDFThumbnailViewer_resetView() { + this.thumbnails = []; + this._pagesRotation = 0; + this._pagesRequests = []; + }, + + setDocument: function PDFThumbnailViewer_setDocument(pdfDocument) { + if (this.pdfDocument) { + // cleanup of the elements and views + var thumbsView = this.container; + while (thumbsView.hasChildNodes()) { + thumbsView.removeChild(thumbsView.lastChild); + } + this._resetView(); + } + + this.pdfDocument = pdfDocument; + if (!pdfDocument) { + return Promise.resolve(); + } + + return pdfDocument.getPage(1).then(function (firstPage) { + var pagesCount = pdfDocument.numPages; + var viewport = firstPage.getViewport(1.0); + for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) { + var thumbnail = new PDFThumbnailView({ + container: this.container, + id: pageNum, + defaultViewport: viewport.clone(), + linkService: this.linkService, + renderingQueue: this.renderingQueue + }); + this.thumbnails.push(thumbnail); + } + }.bind(this)); + }, + + /** + * @param {PDFPageView} pageView + * @returns {PDFPage} + * @private + */ + _ensurePdfPageLoaded: + function PDFThumbnailViewer_ensurePdfPageLoaded(thumbView) { + if (thumbView.pdfPage) { + return Promise.resolve(thumbView.pdfPage); + } + var pageNumber = thumbView.id; + if (this._pagesRequests[pageNumber]) { + return this._pagesRequests[pageNumber]; + } + var promise = this.pdfDocument.getPage(pageNumber).then( + function (pdfPage) { + thumbView.setPdfPage(pdfPage); + this._pagesRequests[pageNumber] = null; + return pdfPage; + }.bind(this)); + this._pagesRequests[pageNumber] = promise; + return promise; + }, + + ensureThumbnailVisible: + function PDFThumbnailViewer_ensureThumbnailVisible(page) { + // Ensure that the thumbnail of the current page is visible + // when switching from another view. + scrollIntoView(document.getElementById('thumbnailContainer' + page)); + }, + + forceRendering: function () { + var visibleThumbs = this._getVisibleThumbs(); + var thumbView = this.renderingQueue.getHighestPriority(visibleThumbs, + this.thumbnails, + this.scroll.down); + if (thumbView) { + this._ensurePdfPageLoaded(thumbView).then(function () { + this.renderingQueue.renderView(thumbView); + }.bind(this)); + return true; + } + return false; + } + }; + + return PDFThumbnailViewer; +})(); + + +/** + * @typedef {Object} PDFOutlineViewOptions + * @property {HTMLDivElement} container - The viewer element. + * @property {Array} outline - An array of outline objects. + * @property {IPDFLinkService} linkService - The navigation/linking service. + */ + +/** + * @class + */ +var PDFOutlineView = (function PDFOutlineViewClosure() { + /** + * @constructs PDFOutlineView + * @param {PDFOutlineViewOptions} options + */ + function PDFOutlineView(options) { + this.container = options.container; + this.outline = options.outline; + this.linkService = options.linkService; + } + + PDFOutlineView.prototype = { + reset: function PDFOutlineView_reset() { + var container = this.container; + while (container.firstChild) { + container.removeChild(container.firstChild); + } + }, + + /** + * @private + */ + _bindLink: function PDFOutlineView_bindLink(element, item) { + var linkService = this.linkService; + element.href = linkService.getDestinationHash(item.dest); + element.onclick = function goToDestination(e) { + linkService.navigateTo(item.dest); + return false; + }; + }, + + render: function PDFOutlineView_render() { + var outline = this.outline; + + this.reset(); + + if (!outline) { + return; + } + + var queue = [{ parent: this.container, items: this.outline }]; + while (queue.length > 0) { + var levelData = queue.shift(); + for (var i = 0, len = levelData.items.length; i < len; i++) { + var item = levelData.items[i]; + var div = document.createElement('div'); + div.className = 'outlineItem'; + var element = document.createElement('a'); + this._bindLink(element, item); + element.textContent = item.title; + div.appendChild(element); + + if (item.items.length > 0) { + var itemsDiv = document.createElement('div'); + itemsDiv.className = 'outlineItems'; + div.appendChild(itemsDiv); + queue.push({ parent: itemsDiv, items: item.items }); + } + + levelData.parent.appendChild(div); + } + } + } + }; + + return PDFOutlineView; +})(); + + +/** + * @typedef {Object} PDFAttachmentViewOptions + * @property {HTMLDivElement} container - The viewer element. + * @property {Array} attachments - An array of attachment objects. + * @property {DownloadManager} downloadManager - The download manager. + */ + +/** + * @class + */ +var PDFAttachmentView = (function PDFAttachmentViewClosure() { + /** + * @constructs PDFAttachmentView + * @param {PDFAttachmentViewOptions} options + */ + function PDFAttachmentView(options) { + this.container = options.container; + this.attachments = options.attachments; + this.downloadManager = options.downloadManager; + } + + PDFAttachmentView.prototype = { + reset: function PDFAttachmentView_reset() { + var container = this.container; + while (container.firstChild) { + container.removeChild(container.firstChild); + } + }, + + /** + * @private + */ + _bindLink: function PDFAttachmentView_bindLink(button, content, filename) { + button.onclick = function downloadFile(e) { + this.downloadManager.downloadData(content, filename, ''); + return false; + }.bind(this); + }, + + render: function PDFAttachmentView_render() { + var attachments = this.attachments; + + this.reset(); + + if (!attachments) { + return; + } + + var names = Object.keys(attachments).sort(function(a, b) { + return a.toLowerCase().localeCompare(b.toLowerCase()); + }); + for (var i = 0, len = names.length; i < len; i++) { + var item = attachments[names[i]]; + var filename = getFileName(item.filename); + var div = document.createElement('div'); + div.className = 'attachmentsItem'; + var button = document.createElement('button'); + this._bindLink(button, item.content, filename); + button.textContent = filename; + div.appendChild(button); + this.container.appendChild(div); + } + } + }; + + return PDFAttachmentView; })(); @@ -4764,67 +5548,18 @@ var PDFViewerApplication = { }, initPassiveLoading: function pdfViewInitPassiveLoading() { - var pdfDataRangeTransportReadyResolve; - var pdfDataRangeTransportReady = new Promise(function (resolve) { - pdfDataRangeTransportReadyResolve = resolve; - }); - var pdfDataRangeTransport = { - rangeListeners: [], - progressListeners: [], - progressiveReadListeners: [], - ready: pdfDataRangeTransportReady, - - addRangeListener: function PdfDataRangeTransport_addRangeListener( - listener) { - this.rangeListeners.push(listener); - }, - - addProgressListener: function PdfDataRangeTransport_addProgressListener( - listener) { - this.progressListeners.push(listener); - }, - - addProgressiveReadListener: - function PdfDataRangeTransport_addProgressiveReadListener(listener) { - this.progressiveReadListeners.push(listener); - }, - - onDataRange: function PdfDataRangeTransport_onDataRange(begin, chunk) { - var listeners = this.rangeListeners; - for (var i = 0, n = listeners.length; i < n; ++i) { - listeners[i](begin, chunk); - } - }, - - onDataProgress: function PdfDataRangeTransport_onDataProgress(loaded) { - this.ready.then(function () { - var listeners = this.progressListeners; - for (var i = 0, n = listeners.length; i < n; ++i) { - listeners[i](loaded); - } - }.bind(this)); - }, - - onDataProgressiveRead: - function PdfDataRangeTransport_onDataProgress(chunk) { - this.ready.then(function () { - var listeners = this.progressiveReadListeners; - for (var i = 0, n = listeners.length; i < n; ++i) { - listeners[i](chunk); - } - }.bind(this)); - }, - - transportReady: function PdfDataRangeTransport_transportReady() { - pdfDataRangeTransportReadyResolve(); - }, - - requestDataRange: function PdfDataRangeTransport_requestDataRange( - begin, end) { - FirefoxCom.request('requestDataRange', { begin: begin, end: end }); - } + function FirefoxComDataRangeTransport(length, initialData) { + PDFJS.PDFDataRangeTransport.call(this, length, initialData); + } + FirefoxComDataRangeTransport.prototype = + Object.create(PDFJS.PDFDataRangeTransport.prototype); + FirefoxComDataRangeTransport.prototype.requestDataRange = + function FirefoxComDataRangeTransport_requestDataRange(begin, end) { + FirefoxCom.request('requestDataRange', { begin: begin, end: end }); }; + var pdfDataRangeTransport; + window.addEventListener('message', function windowMessage(e) { if (e.source !== null) { // The message MUST originate from Chrome code. @@ -4838,11 +5573,15 @@ var PDFViewerApplication = { } switch (args.pdfjsLoadAction) { case 'supportsRangedLoading': + pdfDataRangeTransport = + new FirefoxComDataRangeTransport(args.length, args.data); + PDFViewerApplication.open(args.pdfUrl, 0, undefined, - pdfDataRangeTransport, { - length: args.length, - initialData: args.data - }); + pdfDataRangeTransport); + + if (args.length) { + DocumentProperties.setFileSize(args.length); + } break; case 'range': pdfDataRangeTransport.onDataRange(args.begin, args.chunk); @@ -5344,15 +6083,16 @@ var PDFViewerApplication = { var promises = [pagesPromise, this.animationStartedPromise]; Promise.all(promises).then(function() { pdfDocument.getOutline().then(function(outline) { - var outlineView = document.getElementById('outlineView'); - self.outline = new DocumentOutlineView({ + var container = document.getElementById('outlineView'); + self.outline = new PDFOutlineView({ + container: container, outline: outline, - outlineView: outlineView, linkService: self }); + self.outline.render(); document.getElementById('viewOutline').disabled = !outline; - if (!outline && !outlineView.classList.contains('hidden')) { + if (!outline && !container.classList.contains('hidden')) { self.switchSidebarView('thumbs'); } if (outline && @@ -5361,14 +6101,16 @@ var PDFViewerApplication = { } }); pdfDocument.getAttachments().then(function(attachments) { - var attachmentsView = document.getElementById('attachmentsView'); - self.attachments = new DocumentAttachmentsView({ + var container = document.getElementById('attachmentsView'); + self.attachments = new PDFAttachmentView({ + container: container, attachments: attachments, - attachmentsView: attachmentsView + downloadManager: new DownloadManager() }); + self.attachments.render(); document.getElementById('viewAttachments').disabled = !attachments; - if (!attachments && !attachmentsView.classList.contains('hidden')) { + if (!attachments && !container.classList.contains('hidden')) { self.switchSidebarView('thumbs'); } if (attachments && @@ -5684,7 +6426,6 @@ var PDFViewerApplication = { rotatePages: function pdfViewRotatePages(delta) { var pageNumber = this.page; - this.pageRotation = (this.pageRotation + 360 + delta) % 360; this.pdfViewer.pagesRotation = this.pageRotation; this.pdfThumbnailViewer.pagesRotation = this.pageRotation; @@ -5764,455 +6505,6 @@ var PDFViewerApplication = { }; -var THUMBNAIL_SCROLL_MARGIN = -19; - -/** - * @constructor - * @param container - * @param id - * @param defaultViewport - * @param linkService - * @param renderingQueue - * @param pageSource - * - * @implements {IRenderableView} - */ -var ThumbnailView = function thumbnailView(container, id, defaultViewport, - linkService, renderingQueue, - pageSource) { - var anchor = document.createElement('a'); - anchor.href = linkService.getAnchorUrl('#page=' + id); - anchor.title = mozL10n.get('thumb_page_title', {page: id}, 'Page {{page}}'); - anchor.onclick = function stopNavigation() { - linkService.page = id; - return false; - }; - - this.pdfPage = undefined; - this.viewport = defaultViewport; - this.pdfPageRotate = defaultViewport.rotation; - - this.rotation = 0; - this.pageWidth = this.viewport.width; - this.pageHeight = this.viewport.height; - this.pageRatio = this.pageWidth / this.pageHeight; - this.id = id; - this.renderingId = 'thumbnail' + id; - - this.canvasWidth = 98; - this.canvasHeight = this.canvasWidth / this.pageWidth * this.pageHeight; - this.scale = (this.canvasWidth / this.pageWidth); - - var div = this.el = document.createElement('div'); - div.id = 'thumbnailContainer' + id; - div.className = 'thumbnail'; - - if (id === 1) { - // Highlight the thumbnail of the first page when no page number is - // specified (or exists in cache) when the document is loaded. - div.classList.add('selected'); - } - - var ring = document.createElement('div'); - ring.className = 'thumbnailSelectionRing'; - ring.style.width = this.canvasWidth + 'px'; - ring.style.height = this.canvasHeight + 'px'; - - div.appendChild(ring); - anchor.appendChild(div); - container.appendChild(anchor); - - this.hasImage = false; - this.renderingState = RenderingStates.INITIAL; - this.renderingQueue = renderingQueue; - this.pageSource = pageSource; - - this.setPdfPage = function thumbnailViewSetPdfPage(pdfPage) { - this.pdfPage = pdfPage; - this.pdfPageRotate = pdfPage.rotate; - var totalRotation = (this.rotation + this.pdfPageRotate) % 360; - this.viewport = pdfPage.getViewport(1, totalRotation); - this.update(); - }; - - this.update = function thumbnailViewUpdate(rotation) { - if (rotation !== undefined) { - this.rotation = rotation; - } - var totalRotation = (this.rotation + this.pdfPageRotate) % 360; - this.viewport = this.viewport.clone({ - scale: 1, - rotation: totalRotation - }); - this.pageWidth = this.viewport.width; - this.pageHeight = this.viewport.height; - this.pageRatio = this.pageWidth / this.pageHeight; - - this.canvasHeight = this.canvasWidth / this.pageWidth * this.pageHeight; - this.scale = (this.canvasWidth / this.pageWidth); - - div.removeAttribute('data-loaded'); - ring.textContent = ''; - ring.style.width = this.canvasWidth + 'px'; - ring.style.height = this.canvasHeight + 'px'; - - this.hasImage = false; - this.renderingState = RenderingStates.INITIAL; - this.resume = null; - }; - - this.getPageDrawContext = function thumbnailViewGetPageDrawContext() { - var canvas = document.createElement('canvas'); - canvas.id = 'thumbnail' + id; - - canvas.width = this.canvasWidth; - canvas.height = this.canvasHeight; - canvas.className = 'thumbnailImage'; - canvas.setAttribute('aria-label', mozL10n.get('thumb_page_canvas', - {page: id}, 'Thumbnail of Page {{page}}')); - - div.setAttribute('data-loaded', true); - - ring.appendChild(canvas); - - var ctx = canvas.getContext('2d'); - ctx.save(); - ctx.fillStyle = 'rgb(255, 255, 255)'; - ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight); - ctx.restore(); - return ctx; - }; - - this.drawingRequired = function thumbnailViewDrawingRequired() { - return !this.hasImage; - }; - - this.draw = function thumbnailViewDraw(callback) { - if (!this.pdfPage) { - var promise = this.pageSource.getPage(this.id); - promise.then(function(pdfPage) { - this.setPdfPage(pdfPage); - this.draw(callback); - }.bind(this)); - return; - } - - if (this.renderingState !== RenderingStates.INITIAL) { - console.error('Must be in new state before drawing'); - } - - this.renderingState = RenderingStates.RUNNING; - if (this.hasImage) { - callback(); - return; - } - - var self = this; - var ctx = this.getPageDrawContext(); - var drawViewport = this.viewport.clone({ scale: this.scale }); - var renderContext = { - canvasContext: ctx, - viewport: drawViewport, - continueCallback: function(cont) { - if (!self.renderingQueue.isHighestPriority(self)) { - self.renderingState = RenderingStates.PAUSED; - self.resume = function() { - self.renderingState = RenderingStates.RUNNING; - cont(); - }; - return; - } - cont(); - } - }; - this.pdfPage.render(renderContext).promise.then( - function pdfPageRenderCallback() { - self.renderingState = RenderingStates.FINISHED; - callback(); - }, - function pdfPageRenderError(error) { - self.renderingState = RenderingStates.FINISHED; - callback(); - } - ); - this.hasImage = true; - }; - - function getTempCanvas(width, height) { - var tempCanvas = ThumbnailView.tempImageCache; - if (!tempCanvas) { - tempCanvas = document.createElement('canvas'); - ThumbnailView.tempImageCache = tempCanvas; - } - tempCanvas.width = width; - tempCanvas.height = height; - return tempCanvas; - } - - this.setImage = function thumbnailViewSetImage(img) { - if (!this.pdfPage) { - var promise = this.pageSource.getPage(); - promise.then(function(pdfPage) { - this.setPdfPage(pdfPage); - this.setImage(img); - }.bind(this)); - return; - } - if (this.hasImage || !img) { - return; - } - this.renderingState = RenderingStates.FINISHED; - var ctx = this.getPageDrawContext(); - - var reducedImage = img; - var reducedWidth = img.width; - var reducedHeight = img.height; - - // drawImage does an awful job of rescaling the image, doing it gradually - var MAX_SCALE_FACTOR = 2.0; - if (Math.max(img.width / ctx.canvas.width, - img.height / ctx.canvas.height) > MAX_SCALE_FACTOR) { - reducedWidth >>= 1; - reducedHeight >>= 1; - reducedImage = getTempCanvas(reducedWidth, reducedHeight); - var reducedImageCtx = reducedImage.getContext('2d'); - reducedImageCtx.drawImage(img, 0, 0, img.width, img.height, - 0, 0, reducedWidth, reducedHeight); - while (Math.max(reducedWidth / ctx.canvas.width, - reducedHeight / ctx.canvas.height) > MAX_SCALE_FACTOR) { - reducedImageCtx.drawImage(reducedImage, - 0, 0, reducedWidth, reducedHeight, - 0, 0, reducedWidth >> 1, reducedHeight >> 1); - reducedWidth >>= 1; - reducedHeight >>= 1; - } - } - - ctx.drawImage(reducedImage, 0, 0, reducedWidth, reducedHeight, - 0, 0, ctx.canvas.width, ctx.canvas.height); - - this.hasImage = true; - }; -}; - -ThumbnailView.tempImageCache = null; - -/** - * @typedef {Object} PDFThumbnailViewerOptions - * @property {HTMLDivElement} container - The container for the thumbs elements. - * @property {IPDFLinkService} linkService - The navigation/linking service. - * @property {PDFRenderingQueue} renderingQueue - The rendering queue object. - */ - -/** - * Simple viewer control to display thumbs for pages. - * @class - */ -var PDFThumbnailViewer = (function pdfThumbnailViewer() { - /** - * @constructs - * @param {PDFThumbnailViewerOptions} options - */ - function PDFThumbnailViewer(options) { - this.container = options.container; - this.renderingQueue = options.renderingQueue; - this.linkService = options.linkService; - - this.scroll = watchScroll(this.container, this._scrollUpdated.bind(this)); - this._resetView(); - } - - PDFThumbnailViewer.prototype = { - _scrollUpdated: function PDFThumbnailViewer_scrollUpdated() { - this.renderingQueue.renderHighestPriority(); - }, - - getThumbnail: function PDFThumbnailViewer_getThumbnail(index) { - return this.thumbnails[index]; - }, - - _getVisibleThumbs: function PDFThumbnailViewer_getVisibleThumbs() { - return getVisibleElements(this.container, this.thumbnails); - }, - - scrollThumbnailIntoView: function (page) { - var selected = document.querySelector('.thumbnail.selected'); - if (selected) { - selected.classList.remove('selected'); - } - var thumbnail = document.getElementById('thumbnailContainer' + page); - thumbnail.classList.add('selected'); - var visibleThumbs = this._getVisibleThumbs(); - var numVisibleThumbs = visibleThumbs.views.length; - - // If the thumbnail isn't currently visible, scroll it into view. - if (numVisibleThumbs > 0) { - var first = visibleThumbs.first.id; - // Account for only one thumbnail being visible. - var last = (numVisibleThumbs > 1 ? visibleThumbs.last.id : first); - if (page <= first || page >= last) { - scrollIntoView(thumbnail, { top: THUMBNAIL_SCROLL_MARGIN }); - } - } - }, - - get pagesRotation() { - return this._pagesRotation; - }, - - set pagesRotation(rotation) { - this._pagesRotation = rotation; - for (var i = 0, l = this.thumbnails.length; i < l; i++) { - var thumb = this.thumbnails[i]; - thumb.update(rotation); - } - }, - - cleanup: function PDFThumbnailViewer_cleanup() { - ThumbnailView.tempImageCache = null; - }, - - _resetView: function () { - this.thumbnails = []; - this._pagesRotation = 0; - }, - - setDocument: function (pdfDocument) { - if (this.pdfDocument) { - // cleanup of the elements and views - var thumbsView = this.container; - while (thumbsView.hasChildNodes()) { - thumbsView.removeChild(thumbsView.lastChild); - } - this._resetView(); - } - - this.pdfDocument = pdfDocument; - if (!pdfDocument) { - return Promise.resolve(); - } - - return pdfDocument.getPage(1).then(function (firstPage) { - var pagesCount = pdfDocument.numPages; - var viewport = firstPage.getViewport(1.0); - for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) { - var pageSource = new PDFPageSource(pdfDocument, pageNum); - var thumbnail = new ThumbnailView(this.container, pageNum, - viewport.clone(), this.linkService, - this.renderingQueue, pageSource); - this.thumbnails.push(thumbnail); - } - }.bind(this)); - }, - - ensureThumbnailVisible: - function PDFThumbnailViewer_ensureThumbnailVisible(page) { - // Ensure that the thumbnail of the current page is visible - // when switching from another view. - scrollIntoView(document.getElementById('thumbnailContainer' + page)); - }, - - forceRendering: function () { - var visibleThumbs = this._getVisibleThumbs(); - var thumbView = this.renderingQueue.getHighestPriority(visibleThumbs, - this.thumbnails, - this.scroll.down); - if (thumbView) { - this.renderingQueue.renderView(thumbView); - return true; - } - return false; - } - }; - - return PDFThumbnailViewer; -})(); - - -var DocumentOutlineView = function documentOutlineView(options) { - var outline = options.outline; - var outlineView = options.outlineView; - while (outlineView.firstChild) { - outlineView.removeChild(outlineView.firstChild); - } - - if (!outline) { - return; - } - - var linkService = options.linkService; - - function bindItemLink(domObj, item) { - domObj.href = linkService.getDestinationHash(item.dest); - domObj.onclick = function documentOutlineViewOnclick(e) { - linkService.navigateTo(item.dest); - return false; - }; - } - - var queue = [{parent: outlineView, items: outline}]; - while (queue.length > 0) { - var levelData = queue.shift(); - var i, n = levelData.items.length; - for (i = 0; i < n; i++) { - var item = levelData.items[i]; - var div = document.createElement('div'); - div.className = 'outlineItem'; - var a = document.createElement('a'); - bindItemLink(a, item); - a.textContent = item.title; - div.appendChild(a); - - if (item.items.length > 0) { - var itemsDiv = document.createElement('div'); - itemsDiv.className = 'outlineItems'; - div.appendChild(itemsDiv); - queue.push({parent: itemsDiv, items: item.items}); - } - - levelData.parent.appendChild(div); - } - } -}; - - -var DocumentAttachmentsView = function documentAttachmentsView(options) { - var attachments = options.attachments; - var attachmentsView = options.attachmentsView; - while (attachmentsView.firstChild) { - attachmentsView.removeChild(attachmentsView.firstChild); - } - - if (!attachments) { - return; - } - - function bindItemLink(domObj, item) { - domObj.onclick = function documentAttachmentsViewOnclick(e) { - var downloadManager = new DownloadManager(); - downloadManager.downloadData(item.content, getFileName(item.filename), - ''); - return false; - }; - } - - var names = Object.keys(attachments).sort(function(a,b) { - return a.toLowerCase().localeCompare(b.toLowerCase()); - }); - for (var i = 0, ii = names.length; i < ii; i++) { - var item = attachments[names[i]]; - var div = document.createElement('div'); - div.className = 'attachmentsItem'; - var button = document.createElement('button'); - bindItemLink(button, item); - button.textContent = getFileName(item.filename); - div.appendChild(button); - attachmentsView.appendChild(div); - } -}; - - - function webViewerLoad(evt) { PDFViewerApplication.initialize().then(webViewerInitialized); } @@ -6409,20 +6701,15 @@ function webViewerInitialized() { document.addEventListener('DOMContentLoaded', webViewerLoad, true); document.addEventListener('pagerendered', function (e) { - var pageIndex = e.detail.pageNumber - 1; + var pageNumber = e.detail.pageNumber; + var pageIndex = pageNumber - 1; var pageView = PDFViewerApplication.pdfViewer.getPageView(pageIndex); var thumbnailView = PDFViewerApplication.pdfThumbnailViewer. getThumbnail(pageIndex); - thumbnailView.setImage(pageView.canvas); + thumbnailView.setImage(pageView); - if (pageView.textLayer && pageView.textLayer.textDivs && - pageView.textLayer.textDivs.length > 0 && - !PDFViewerApplication.supportsDocumentColors) { - console.error(mozL10n.get('document_colors_disabled', null, - 'PDF documents are not allowed to use their own colors: ' + - '\'Allow pages to choose their own colors\' ' + - 'is deactivated in the browser.')); - PDFViewerApplication.fallback(); + if (PDFJS.pdfBug && Stats.enabled && pageView.stats) { + Stats.add(pageNumber, pageView.stats); } if (pageView.error) { @@ -6443,12 +6730,27 @@ document.addEventListener('pagerendered', function (e) { // If the page is still visible when it has finished rendering, // ensure that the page number input loading indicator is hidden. - if ((pageIndex + 1) === PDFViewerApplication.page) { + if (pageNumber === PDFViewerApplication.page) { var pageNumberInput = document.getElementById('pageNumber'); pageNumberInput.classList.remove(PAGE_NUMBER_LOADING_INDICATOR); } }, true); +document.addEventListener('textlayerrendered', function (e) { + var pageIndex = e.detail.pageNumber - 1; + var pageView = PDFViewerApplication.pdfViewer.getPageView(pageIndex); + + if (pageView.textLayer && pageView.textLayer.textDivs && + pageView.textLayer.textDivs.length > 0 && + !PDFViewerApplication.supportsDocumentColors) { + console.error(mozL10n.get('document_colors_disabled', null, + 'PDF documents are not allowed to use their own colors: ' + + '\'Allow pages to choose their own colors\' ' + + 'is deactivated in the browser.')); + PDFViewerApplication.fallback(); + } +}, true); + window.addEventListener('presentationmodechanged', function (e) { var active = e.detail.active; var switchInProgress = e.detail.switchInProgress; @@ -6608,6 +6910,14 @@ window.addEventListener('pagechange', function pagechange(evt) { document.getElementById('firstPage').disabled = (page <= 1); document.getElementById('lastPage').disabled = (page >= numPages); + // we need to update stats + if (PDFJS.pdfBug && Stats.enabled) { + var pageView = PDFViewerApplication.pdfViewer.getPageView(page - 1); + if (pageView.stats) { + Stats.add(page, pageView.stats); + } + } + // checking if the this.page was called from the updateViewarea function if (evt.updateInProgress) { return; From d19fa5f9dda0bf0d925aadfc11b3e5553be68bd2 Mon Sep 17 00:00:00 2001 From: Mark Banner Date: Fri, 30 Jan 2015 20:49:33 +0000 Subject: [PATCH 087/101] Follow-up to bug 1093780 to fix an uncovered intermittent failure. Make sure we're in offline mode when opening the chat window to stop it accessing the network. rs=MattN over irc --- .../uitour/test/browser_UITour_loop.js | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/browser/components/uitour/test/browser_UITour_loop.js b/browser/components/uitour/test/browser_UITour_loop.js index d2372632279..d3ae6e03493 100644 --- a/browser/components/uitour/test/browser_UITour_loop.js +++ b/browser/components/uitour/test/browser_UITour_loop.js @@ -16,6 +16,16 @@ function test() { UITourTest(); } +function runOffline(fun) { + return (done) => { + Services.io.offline = true; + fun(function onComplete() { + Services.io.offline = false; + done(); + }); + } +} + let tests = [ taskify(function* test_menu_show_hide() { ise(loopButton.open, false, "Menu should initially be closed"); @@ -94,7 +104,7 @@ let tests = [ }); }); }, - function test_notifyLoopChatWindowOpenedClosed(done) { + runOffline(function test_notifyLoopChatWindowOpenedClosed(done) { gContentAPI.observe((event, params) => { is(event, "Loop:ChatWindowOpened", "Check Loop:ChatWindowOpened notification"); gContentAPI.observe((event, params) => { @@ -110,8 +120,8 @@ let tests = [ document.querySelector("#pinnedchats > chatbox").close(); }); LoopRooms.open("fakeTourRoom"); - }, - function test_notifyLoopRoomURLCopied(done) { + }), + runOffline(function test_notifyLoopRoomURLCopied(done) { gContentAPI.observe((event, params) => { is(event, "Loop:ChatWindowOpened", "Loop chat window should've opened"); gContentAPI.observe((event, params) => { @@ -131,8 +141,8 @@ let tests = [ }); setupFakeRoom(); LoopRooms.open("fakeTourRoom"); - }, - function test_notifyLoopRoomURLEmailed(done) { + }), + runOffline(function test_notifyLoopRoomURLEmailed(done) { gContentAPI.observe((event, params) => { is(event, "Loop:ChatWindowOpened", "Loop chat window should've opened"); gContentAPI.observe((event, params) => { @@ -162,7 +172,7 @@ let tests = [ }); }); LoopRooms.open("fakeTourRoom"); - }, + }), taskify(function* test_arrow_panel_position() { ise(loopButton.open, false, "Menu should initially be closed"); let popup = document.getElementById("UITourTooltip"); @@ -274,6 +284,7 @@ if (Services.prefs.getBoolPref("loop.enabled")) { Services.prefs.clearUserPref("loop.gettingStarted.resumeOnFirstJoin"); Services.prefs.clearUserPref("loop.gettingStarted.seen"); Services.prefs.clearUserPref("loop.gettingStarted.url"); + Services.io.offline = false; // Copied from browser/components/loop/test/mochitest/head.js // Remove the iframe after each test. This also avoids mochitest complaining From bc10ba2ce676b58abef150cd98272f63f08d34f0 Mon Sep 17 00:00:00 2001 From: Bobby Holley Date: Fri, 30 Jan 2015 17:45:49 -0800 Subject: [PATCH 088/101] Bug 1127203 - Rename aError to aTolerance. r=mattwoodrow aError is a really misleading name. --- dom/html/TimeRanges.cpp | 12 ++++++------ dom/html/TimeRanges.h | 6 +++--- dom/media/mediasource/MediaSourceReader.cpp | 12 ++++++------ dom/media/mediasource/MediaSourceReader.h | 11 ++++++----- 4 files changed, 21 insertions(+), 20 deletions(-) diff --git a/dom/html/TimeRanges.cpp b/dom/html/TimeRanges.cpp index 1e006a4245b..b1a45631d79 100644 --- a/dom/html/TimeRanges.cpp +++ b/dom/html/TimeRanges.cpp @@ -98,7 +98,7 @@ TimeRanges::GetEndTime() } void -TimeRanges::Normalize(double aError) +TimeRanges::Normalize(double aTolerance) { if (mRanges.Length() >= 2) { nsAutoTArray normalized; @@ -112,7 +112,7 @@ TimeRanges::Normalize(double aError) current.mEnd >= mRanges[i].mEnd) { continue; } - if (current.mEnd + aError >= mRanges[i].mStart) { + if (current.mEnd + aTolerance >= mRanges[i].mStart) { current.mEnd = mRanges[i].mEnd; } else { normalized.AppendElement(current); @@ -127,10 +127,10 @@ TimeRanges::Normalize(double aError) } void -TimeRanges::Union(const TimeRanges* aOtherRanges, double aError) +TimeRanges::Union(const TimeRanges* aOtherRanges, double aTolerance) { mRanges.AppendElements(aOtherRanges->mRanges); - Normalize(aError); + Normalize(aTolerance); } void @@ -156,10 +156,10 @@ TimeRanges::Intersection(const TimeRanges* aOtherRanges) } TimeRanges::index_type -TimeRanges::Find(double aTime, double aError /* = 0 */) +TimeRanges::Find(double aTime, double aTolerance /* = 0 */) { for (index_type i = 0; i < mRanges.Length(); ++i) { - if (aTime < mRanges[i].mEnd && (aTime + aError) >= mRanges[i].mStart) { + if (aTime < mRanges[i].mEnd && (aTime + aTolerance) >= mRanges[i].mStart) { return i; } } diff --git a/dom/html/TimeRanges.h b/dom/html/TimeRanges.h index 161466037ed..f7db15b85d6 100644 --- a/dom/html/TimeRanges.h +++ b/dom/html/TimeRanges.h @@ -42,10 +42,10 @@ public: double GetEndTime(); // See http://www.whatwg.org/html/#normalized-timeranges-object - void Normalize(double aError = 0.0); + void Normalize(double aTolerance = 0.0); // Mutate this TimeRange to be the union of this and aOtherRanges. - void Union(const TimeRanges* aOtherRanges, double aError); + void Union(const TimeRanges* aOtherRanges, double aTolerance); // Mutate this TimeRange to be the intersection of this and aOtherRanges. void Intersection(const TimeRanges* aOtherRanges); @@ -91,7 +91,7 @@ public: typedef nsTArray::index_type index_type; static const index_type NoIndex = index_type(-1); - index_type Find(double aTime, double aError = 0); + index_type Find(double aTime, double aTolerance = 0); bool Contains(double aStart, double aEnd) { index_type target = Find(aStart); diff --git a/dom/media/mediasource/MediaSourceReader.cpp b/dom/media/mediasource/MediaSourceReader.cpp index d35007fece6..fe37dfc8afe 100644 --- a/dom/media/mediasource/MediaSourceReader.cpp +++ b/dom/media/mediasource/MediaSourceReader.cpp @@ -437,7 +437,7 @@ MediaSourceReader::BreakCycles() already_AddRefed MediaSourceReader::SelectReader(int64_t aTarget, - int64_t aError, + int64_t aTolerance, const nsTArray>& aTrackDecoders) { mDecoder->GetReentrantMonitor().AssertCurrentThreadIn(); @@ -450,7 +450,7 @@ MediaSourceReader::SelectReader(int64_t aTarget, nsRefPtr ranges = new dom::TimeRanges(); aTrackDecoders[i]->GetBuffered(ranges); if (ranges->Find(double(aTarget) / USECS_PER_S, - double(aError) / USECS_PER_S) == dom::TimeRanges::NoIndex) { + double(aTolerance) / USECS_PER_S) == dom::TimeRanges::NoIndex) { MSE_DEBUGV("MediaSourceReader(%p)::SelectReader(%lld) newReader=%p target not in ranges=%s", this, aTarget, newReader.get(), DumpTimeRanges(ranges).get()); continue; @@ -472,14 +472,14 @@ MediaSourceReader::HaveData(int64_t aTarget, MediaData::Type aType) } MediaSourceReader::SwitchReaderResult -MediaSourceReader::SwitchAudioReader(int64_t aTarget, int64_t aError) +MediaSourceReader::SwitchAudioReader(int64_t aTarget, int64_t aTolerance) { ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); // XXX: Can't handle adding an audio track after ReadMetadata. if (!mAudioTrack) { return READER_ERROR; } - nsRefPtr newReader = SelectReader(aTarget, aError, mAudioTrack->Decoders()); + nsRefPtr newReader = SelectReader(aTarget, aTolerance, mAudioTrack->Decoders()); if (newReader && newReader != mAudioReader) { mAudioReader->SetIdle(); mAudioReader = newReader; @@ -490,14 +490,14 @@ MediaSourceReader::SwitchAudioReader(int64_t aTarget, int64_t aError) } MediaSourceReader::SwitchReaderResult -MediaSourceReader::SwitchVideoReader(int64_t aTarget, int64_t aError) +MediaSourceReader::SwitchVideoReader(int64_t aTarget, int64_t aTolerance) { ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); // XXX: Can't handle adding a video track after ReadMetadata. if (!mVideoTrack) { return READER_ERROR; } - nsRefPtr newReader = SelectReader(aTarget, aError, mVideoTrack->Decoders()); + nsRefPtr newReader = SelectReader(aTarget, aTolerance, mVideoTrack->Decoders()); if (newReader && newReader != mVideoReader) { mVideoReader->SetIdle(); mVideoReader = newReader; diff --git a/dom/media/mediasource/MediaSourceReader.h b/dom/media/mediasource/MediaSourceReader.h index 8c2538044be..4e3a012b8a9 100644 --- a/dom/media/mediasource/MediaSourceReader.h +++ b/dom/media/mediasource/MediaSourceReader.h @@ -155,15 +155,16 @@ public: private: // Switch the current audio/video reader to the reader that - // contains aTarget (or up to aError after target). Both - // aTarget and aError are in microseconds. + // contains aTarget (or up to aTolerance after target). Both + // aTarget and aTolerance are in microseconds. enum SwitchReaderResult { READER_ERROR = -1, READER_EXISTING = 0, READER_NEW = 1, }; - SwitchReaderResult SwitchAudioReader(int64_t aTarget, int64_t aError = 0); - SwitchReaderResult SwitchVideoReader(int64_t aTarget, int64_t aError = 0); + + SwitchReaderResult SwitchAudioReader(int64_t aTarget, int64_t aTolerance = 0); + SwitchReaderResult SwitchVideoReader(int64_t aTarget, int64_t aTolerance = 0); void DoAudioRequest(); void DoVideoRequest(); @@ -199,7 +200,7 @@ private: // Return a reader from the set available in aTrackDecoders that has data // available in the range requested by aTarget. already_AddRefed SelectReader(int64_t aTarget, - int64_t aError, + int64_t aTolerance, const nsTArray>& aTrackDecoders); bool HaveData(int64_t aTarget, MediaData::Type aType); From f9cd69df6edbda8100187f9bc5548d5f60156508 Mon Sep 17 00:00:00 2001 From: Bobby Holley Date: Fri, 30 Jan 2015 17:45:49 -0800 Subject: [PATCH 089/101] Bug 1127203 - Be more consistent about when and how we apply the fuzz factor. r=mattwoodrow --- dom/media/mediasource/MediaSourceReader.cpp | 34 +++++++++++++-------- dom/media/mediasource/MediaSourceReader.h | 4 +-- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/dom/media/mediasource/MediaSourceReader.cpp b/dom/media/mediasource/MediaSourceReader.cpp index fe37dfc8afe..9452d775a84 100644 --- a/dom/media/mediasource/MediaSourceReader.cpp +++ b/dom/media/mediasource/MediaSourceReader.cpp @@ -225,10 +225,8 @@ MediaSourceReader::OnAudioNotDecoded(NotDecodedReason aReason) AdjustEndTime(&mLastAudioTime, mAudioReader); } - // See if we can find a different reader that can pick up where we left off. We use the - // EOS_FUZZ_US to allow for the fact that our end time can be inaccurate due to bug - // 1065207. - if (SwitchAudioReader(mLastAudioTime, EOS_FUZZ_US) == READER_NEW) { + // See if we can find a different reader that can pick up where we left off. + if (SwitchAudioReader(mLastAudioTime) == READER_NEW) { mAudioSeekRequest.Begin(mAudioReader->Seek(mLastAudioTime, 0) ->RefableThen(GetTaskQueue(), __func__, this, &MediaSourceReader::CompleteAudioSeekAndDoRequest, @@ -337,10 +335,8 @@ MediaSourceReader::OnVideoNotDecoded(NotDecodedReason aReason) AdjustEndTime(&mLastVideoTime, mVideoReader); } - // See if we can find a different reader that can pick up where we left off. We use the - // EOS_FUZZ_US to allow for the fact that our end time can be inaccurate due to bug - // 1065207. - if (SwitchVideoReader(mLastVideoTime, EOS_FUZZ_US) == READER_NEW) { + // See if we can find a different reader that can pick up where we left off. + if (SwitchVideoReader(mLastVideoTime) == READER_NEW) { mVideoSeekRequest.Begin(mVideoReader->Seek(mLastVideoTime, 0) ->RefableThen(GetTaskQueue(), __func__, this, &MediaSourceReader::CompleteVideoSeekAndDoRequest, @@ -472,14 +468,21 @@ MediaSourceReader::HaveData(int64_t aTarget, MediaData::Type aType) } MediaSourceReader::SwitchReaderResult -MediaSourceReader::SwitchAudioReader(int64_t aTarget, int64_t aTolerance) +MediaSourceReader::SwitchAudioReader(int64_t aTarget) { ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); // XXX: Can't handle adding an audio track after ReadMetadata. if (!mAudioTrack) { return READER_ERROR; } - nsRefPtr newReader = SelectReader(aTarget, aTolerance, mAudioTrack->Decoders()); + + // We first search without the tolerance and then search with it, so that, in + // the case of perfectly-aligned data, we don't prematurely jump to a new + // reader and skip the last few samples of the current one. + nsRefPtr newReader = SelectReader(aTarget, /* aTolerance = */ 0, mAudioTrack->Decoders()); + if (!newReader) { + newReader = SelectReader(aTarget, EOS_FUZZ_US, mAudioTrack->Decoders()); + } if (newReader && newReader != mAudioReader) { mAudioReader->SetIdle(); mAudioReader = newReader; @@ -490,14 +493,21 @@ MediaSourceReader::SwitchAudioReader(int64_t aTarget, int64_t aTolerance) } MediaSourceReader::SwitchReaderResult -MediaSourceReader::SwitchVideoReader(int64_t aTarget, int64_t aTolerance) +MediaSourceReader::SwitchVideoReader(int64_t aTarget) { ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); // XXX: Can't handle adding a video track after ReadMetadata. if (!mVideoTrack) { return READER_ERROR; } - nsRefPtr newReader = SelectReader(aTarget, aTolerance, mVideoTrack->Decoders()); + + // We first search without the tolerance and then search with it, so that, in + // the case of perfectly-aligned data, we don't prematurely jump to a new + // reader and skip the last few samples of the current one. + nsRefPtr newReader = SelectReader(aTarget, /* aTolerance = */ 0, mVideoTrack->Decoders()); + if (!newReader) { + newReader = SelectReader(aTarget, EOS_FUZZ_US, mVideoTrack->Decoders()); + } if (newReader && newReader != mVideoReader) { mVideoReader->SetIdle(); mVideoReader = newReader; diff --git a/dom/media/mediasource/MediaSourceReader.h b/dom/media/mediasource/MediaSourceReader.h index 4e3a012b8a9..99f427f936f 100644 --- a/dom/media/mediasource/MediaSourceReader.h +++ b/dom/media/mediasource/MediaSourceReader.h @@ -163,8 +163,8 @@ private: READER_NEW = 1, }; - SwitchReaderResult SwitchAudioReader(int64_t aTarget, int64_t aTolerance = 0); - SwitchReaderResult SwitchVideoReader(int64_t aTarget, int64_t aTolerance = 0); + SwitchReaderResult SwitchAudioReader(int64_t aTarget); + SwitchReaderResult SwitchVideoReader(int64_t aTarget); void DoAudioRequest(); void DoVideoRequest(); From b8cefec2b4847733bb4fd0d0c571e2e8eb02f44e Mon Sep 17 00:00:00 2001 From: Bobby Holley Date: Fri, 30 Jan 2015 17:45:49 -0800 Subject: [PATCH 090/101] Bug 1127203 - Use the tolerance value in TrackBuffersContainTime so that seeking operates with tolerance too. r=mattwoodrow --- dom/media/mediasource/MediaSourceReader.cpp | 4 ++-- dom/media/mediasource/TrackBuffer.cpp | 5 +++-- dom/media/mediasource/TrackBuffer.h | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/dom/media/mediasource/MediaSourceReader.cpp b/dom/media/mediasource/MediaSourceReader.cpp index 9452d775a84..0ebba13b962 100644 --- a/dom/media/mediasource/MediaSourceReader.cpp +++ b/dom/media/mediasource/MediaSourceReader.cpp @@ -642,10 +642,10 @@ bool MediaSourceReader::TrackBuffersContainTime(int64_t aTime) { ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); - if (mAudioTrack && !mAudioTrack->ContainsTime(aTime)) { + if (mAudioTrack && !mAudioTrack->ContainsTime(aTime, EOS_FUZZ_US)) { return false; } - if (mVideoTrack && !mVideoTrack->ContainsTime(aTime)) { + if (mVideoTrack && !mVideoTrack->ContainsTime(aTime, EOS_FUZZ_US)) { return false; } return true; diff --git a/dom/media/mediasource/TrackBuffer.cpp b/dom/media/mediasource/TrackBuffer.cpp index 0541afe70b1..fdae40373e2 100644 --- a/dom/media/mediasource/TrackBuffer.cpp +++ b/dom/media/mediasource/TrackBuffer.cpp @@ -584,13 +584,14 @@ TrackBuffer::IsReady() } bool -TrackBuffer::ContainsTime(int64_t aTime) +TrackBuffer::ContainsTime(int64_t aTime, int64_t aTolerance) { ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor()); for (uint32_t i = 0; i < mInitializedDecoders.Length(); ++i) { nsRefPtr r = new dom::TimeRanges(); mInitializedDecoders[i]->GetBuffered(r); - if (r->Find(double(aTime) / USECS_PER_S) != dom::TimeRanges::NoIndex) { + if (r->Find(double(aTime) / USECS_PER_S, + double(aTolerance) / USECS_PER_S) != dom::TimeRanges::NoIndex) { return true; } } diff --git a/dom/media/mediasource/TrackBuffer.h b/dom/media/mediasource/TrackBuffer.h index b025aa39d5a..841e4c66e82 100644 --- a/dom/media/mediasource/TrackBuffer.h +++ b/dom/media/mediasource/TrackBuffer.h @@ -76,7 +76,7 @@ public: // Returns true if any of the decoders managed by this track buffer // contain aTime in their buffered ranges. - bool ContainsTime(int64_t aTime); + bool ContainsTime(int64_t aTime, int64_t aTolerance); void BreakCycles(); From 4ebdb6385a2e68897fa548755c527c9ca4e5304d Mon Sep 17 00:00:00 2001 From: Bobby Holley Date: Fri, 30 Jan 2015 17:45:49 -0800 Subject: [PATCH 091/101] Bug 1127203 - Disable mediasource-play-then-seek-back.html.ini. r=me Previously it just timed out. My patch fixes the timeout, but then causes it to pass/fail intermittently. Disabling and filing a followup bug. --- .../media-source/mediasource-play-then-seek-back.html.ini | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/testing/web-platform/meta/media-source/mediasource-play-then-seek-back.html.ini b/testing/web-platform/meta/media-source/mediasource-play-then-seek-back.html.ini index 123980210d0..18e011b21a2 100644 --- a/testing/web-platform/meta/media-source/mediasource-play-then-seek-back.html.ini +++ b/testing/web-platform/meta/media-source/mediasource-play-then-seek-back.html.ini @@ -1,6 +1,4 @@ [mediasource-play-then-seek-back.html] type: testharness - expected: TIMEOUT - [Test playing then seeking back.] - expected: TIMEOUT + disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1128069 From c444bd244051002a1fd4c0f216fb6ada3c9875e2 Mon Sep 17 00:00:00 2001 From: Martin Thomson Date: Fri, 30 Jan 2015 13:00:29 -0800 Subject: [PATCH 092/101] Bug 1063290 - Disabling extended gUM tests on gonk for intermittent failures, r=jib --- dom/media/tests/mochitest/mochitest.ini | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/dom/media/tests/mochitest/mochitest.ini b/dom/media/tests/mochitest/mochitest.ini index 73dab5a6a9a..a880122c2af 100644 --- a/dom/media/tests/mochitest/mochitest.ini +++ b/dom/media/tests/mochitest/mochitest.ini @@ -43,20 +43,29 @@ skip-if = (toolkit == 'gonk' && debug) # debug-only failure, turned an intermitt [test_getUserMedia_constraints.html] skip-if = toolkit == 'gonk' || toolkit == 'android' # Bug 907352, backwards-compatible behavior on mobile only [test_getUserMedia_constraints_mobile.html] -skip-if = toolkit != 'gonk' && toolkit != 'android' # Bug 907352, backwards-compatible behavior on mobile only +skip-if = toolkit != 'android' # Bug 1063290, intermittent timeout # Bug 907352, backwards-compatible behavior on mobile only [test_getUserMedia_callbacks.html] +skip-if = toolkit == 'gonk' # Bug 1063290, intermittent timeout [test_getUserMedia_gumWithinGum.html] +skip-if = toolkit == 'gonk' # Bug 1063290, intermittent timeout [test_getUserMedia_playAudioTwice.html] +skip-if = toolkit == 'gonk' # Bug 1063290, intermittent timeout [test_getUserMedia_playVideoAudioTwice.html] -skip-if = (toolkit == 'gonk' && debug) # debug-only failure; bug 926558 +skip-if = toolkit == 'gonk' # Bug 1063290, intermittent timeout # bug 926558, debug-only failure [test_getUserMedia_playVideoTwice.html] +skip-if = toolkit == 'gonk' # Bug 1063290, intermittent timeout [test_getUserMedia_stopAudioStream.html] +skip-if = toolkit == 'gonk' # Bug 1063290, intermittent timeout [test_getUserMedia_stopAudioStreamWithFollowupAudio.html] +skip-if = toolkit == 'gonk' # Bug 1063290, intermittent timeout [test_getUserMedia_stopVideoAudioStream.html] -skip-if = (toolkit == 'gonk' && debug) # debug-only failure +skip-if = toolkit == 'gonk' # Bug 1063290, intermittent timeout # bug 926558, debug-only failure [test_getUserMedia_stopVideoAudioStreamWithFollowupVideoAudio.html] +skip-if = toolkit == 'gonk' # Bug 1063290, intermittent timeout [test_getUserMedia_stopVideoStream.html] +skip-if = toolkit == 'gonk' # Bug 1063290, intermittent timeout [test_getUserMedia_stopVideoStreamWithFollowupVideo.html] +skip-if = toolkit == 'gonk' # Bug 1063290, intermittent timeout [test_getUserMedia_peerIdentity.html] skip-if = toolkit == 'gonk' # b2g(Bug 1021776, too --ing slow on b2g) [test_peerConnection_addCandidateInHaveLocalOffer.html] From c0693fbe23c3e2353c31683bae1ccd8b2400dac8 Mon Sep 17 00:00:00 2001 From: Swapnil R Patil Date: Fri, 30 Jan 2015 22:36:34 +0100 Subject: [PATCH 093/101] Bug 1085428 - Replaced do_log_info with do_print. r=mak --- .../places/tests/unit/test_421483.js | 12 +- .../tests/unit/test_browserGlue_prefs.js | 34 ++-- ...erGlue_urlbar_defaultbehavior_migration.js | 16 +- .../test_1016953-renaming-uncompressed.js | 4 +- .../test_992901-backup-unsorted-hierarchy.js | 6 +- .../test_997030-bookmarks-html-encode.js | 2 +- .../tests/bookmarks/test_async_observers.js | 4 +- .../bookmarks/test_nsINavBookmarkObserver.js | 2 +- .../tests/favicons/test_replaceFaviconData.js | 12 +- .../test_replaceFaviconDataFromDataURL.js | 16 +- .../components/places/tests/head_common.js | 11 -- .../places/tests/inline/head_autocomplete.js | 4 +- .../tests/network/test_history_redirects.js | 10 +- .../places/tests/queries/test_tags.js | 148 +++++++++--------- .../unifiedcomplete/head_autocomplete.js | 14 +- .../tests/unifiedcomplete/test_416211.js | 2 +- .../tests/unifiedcomplete/test_416214.js | 2 +- .../tests/unifiedcomplete/test_417798.js | 10 +- .../tests/unifiedcomplete/test_418257.js | 10 +- .../tests/unifiedcomplete/test_422277.js | 2 +- .../test_autoFill_default_behavior.js | 52 +++--- .../test_autocomplete_functional.js | 22 +-- .../test_avoid_middle_complete.js | 22 +-- .../test_avoid_stripping_to_empty_tokens.js | 2 +- .../tests/unifiedcomplete/test_casing.js | 22 +-- .../tests/unifiedcomplete/test_do_not_trim.js | 12 +- .../test_download_embed_bookmarks.js | 12 +- .../tests/unifiedcomplete/test_dupe_urls.js | 2 +- .../unifiedcomplete/test_empty_search.js | 10 +- .../tests/unifiedcomplete/test_enabled.js | 6 +- .../tests/unifiedcomplete/test_escape_self.js | 4 +- .../unifiedcomplete/test_ignore_protocol.js | 2 +- .../unifiedcomplete/test_keyword_search.js | 16 +- .../test_keyword_search_actions.js | 16 +- .../tests/unifiedcomplete/test_keywords.js | 10 +- .../unifiedcomplete/test_match_beginning.js | 10 +- .../unifiedcomplete/test_multi_word_search.js | 12 +- .../tests/unifiedcomplete/test_queryurl.js | 8 +- .../test_searchEngine_current.js | 8 +- .../unifiedcomplete/test_searchEngine_host.js | 2 +- .../test_searchEngine_restyle.js | 4 +- .../unifiedcomplete/test_special_search.js | 76 ++++----- .../unifiedcomplete/test_swap_protocol.js | 34 ++-- .../tests/unifiedcomplete/test_tabmatches.js | 22 +-- .../tests/unifiedcomplete/test_trimming.js | 44 +++--- .../tests/unifiedcomplete/test_typed.js | 12 +- .../tests/unifiedcomplete/test_visiturl.js | 10 +- .../test_word_boundary_search.js | 28 ++-- .../unifiedcomplete/test_zero_frecency.js | 4 +- .../places/tests/unit/test_412132.js | 30 ++-- .../test_PlacesSearchAutocompleteProvider.js | 2 +- .../tests/unit/test_async_history_api.js | 18 +-- .../places/tests/unit/test_isURIVisited.js | 6 +- .../places/tests/unit/test_isvisited.js | 2 +- .../unit/test_removeVisitsByTimeframe.js | 114 +++++++------- .../places/tests/unit/test_telemetry.js | 2 +- .../unit/test_update_frecency_after_delete.js | 40 ++--- .../tests/unit/test_utils_backups_create.js | 2 +- 58 files changed, 505 insertions(+), 516 deletions(-) diff --git a/browser/components/places/tests/unit/test_421483.js b/browser/components/places/tests/unit/test_421483.js index 2f3a4e7b7a9..401bf66051a 100644 --- a/browser/components/places/tests/unit/test_421483.js +++ b/browser/components/places/tests/unit/test_421483.js @@ -24,7 +24,7 @@ add_task(function smart_bookmarks_disabled() { let smartBookmarkItemIds = PlacesUtils.annotations.getItemsWithAnnotation(SMART_BOOKMARKS_ANNO); do_check_eq(smartBookmarkItemIds.length, 0); - do_log_info("check that pref has not been bumped up"); + do_print("check that pref has not been bumped up"); do_check_eq(Services.prefs.getIntPref("browser.places.smartBookmarksVersion"), -1); }); @@ -34,7 +34,7 @@ add_task(function create_smart_bookmarks() { let smartBookmarkItemIds = PlacesUtils.annotations.getItemsWithAnnotation(SMART_BOOKMARKS_ANNO); do_check_neq(smartBookmarkItemIds.length, 0); - do_log_info("check that pref has been bumped up"); + do_print("check that pref has been bumped up"); do_check_true(Services.prefs.getIntPref("browser.places.smartBookmarksVersion") > 0); }); @@ -42,14 +42,14 @@ add_task(function remove_smart_bookmark_and_restore() { let smartBookmarkItemIds = PlacesUtils.annotations.getItemsWithAnnotation(SMART_BOOKMARKS_ANNO); let smartBookmarksCount = smartBookmarkItemIds.length; - do_log_info("remove one smart bookmark and restore"); + do_print("remove one smart bookmark and restore"); PlacesUtils.bookmarks.removeItem(smartBookmarkItemIds[0]); Services.prefs.setIntPref("browser.places.smartBookmarksVersion", 0); gluesvc.ensurePlacesDefaultQueriesInitialized(); smartBookmarkItemIds = PlacesUtils.annotations.getItemsWithAnnotation(SMART_BOOKMARKS_ANNO); do_check_eq(smartBookmarkItemIds.length, smartBookmarksCount); - do_log_info("check that pref has been bumped up"); + do_print("check that pref has been bumped up"); do_check_true(Services.prefs.getIntPref("browser.places.smartBookmarksVersion") > 0); }); @@ -57,7 +57,7 @@ add_task(function move_smart_bookmark_rename_and_restore() { let smartBookmarkItemIds = PlacesUtils.annotations.getItemsWithAnnotation(SMART_BOOKMARKS_ANNO); let smartBookmarksCount = smartBookmarkItemIds.length; - do_log_info("smart bookmark should be restored in place"); + do_print("smart bookmark should be restored in place"); let parent = PlacesUtils.bookmarks.getFolderIdForItem(smartBookmarkItemIds[0]); let oldTitle = PlacesUtils.bookmarks.getItemTitle(smartBookmarkItemIds[0]); // create a subfolder and move inside it @@ -76,6 +76,6 @@ add_task(function move_smart_bookmark_rename_and_restore() { do_check_eq(smartBookmarkItemIds.length, smartBookmarksCount); do_check_eq(PlacesUtils.bookmarks.getFolderIdForItem(smartBookmarkItemIds[0]), newParent); do_check_eq(PlacesUtils.bookmarks.getItemTitle(smartBookmarkItemIds[0]), oldTitle); - do_log_info("check that pref has been bumped up"); + do_print("check that pref has been bumped up"); do_check_true(Services.prefs.getIntPref("browser.places.smartBookmarksVersion") > 0); }); diff --git a/browser/components/places/tests/unit/test_browserGlue_prefs.js b/browser/components/places/tests/unit/test_browserGlue_prefs.js index 383936d88c6..06ab6412412 100644 --- a/browser/components/places/tests/unit/test_browserGlue_prefs.js +++ b/browser/components/places/tests/unit/test_browserGlue_prefs.js @@ -63,7 +63,7 @@ function waitForImportAndSmartBookmarks(aCallback) { function test_import() { - do_log_info("Import from bookmarks.html if importBookmarksHTML is true."); + do_print("Import from bookmarks.html if importBookmarksHTML is true."); remove_all_bookmarks(); // Sanity check: we should not have any bookmark on the toolbar. @@ -86,7 +86,7 @@ function waitForImportAndSmartBookmarks(aCallback) { run_next_test(); }); // Force nsBrowserGlue::_initPlaces(). - do_log_info("Simulate Places init"); + do_print("Simulate Places init"); bg.QueryInterface(Ci.nsIObserver).observe(null, TOPIC_BROWSERGLUE_TEST, TOPICDATA_FORCE_PLACES_INIT); @@ -94,8 +94,8 @@ function waitForImportAndSmartBookmarks(aCallback) { function test_import_noSmartBookmarks() { - do_log_info("import from bookmarks.html, but don't create smart bookmarks \ - if they are disabled"); + do_print("import from bookmarks.html, but don't create smart bookmarks \ + if they are disabled"); remove_all_bookmarks(); // Sanity check: we should not have any bookmark on the toolbar. @@ -119,7 +119,7 @@ function waitForImportAndSmartBookmarks(aCallback) { run_next_test(); }); // Force nsBrowserGlue::_initPlaces(). - do_log_info("Simulate Places init"); + do_print("Simulate Places init"); bg.QueryInterface(Ci.nsIObserver).observe(null, TOPIC_BROWSERGLUE_TEST, TOPICDATA_FORCE_PLACES_INIT); @@ -127,8 +127,8 @@ function waitForImportAndSmartBookmarks(aCallback) { function test_import_autoExport_updatedSmartBookmarks() { - do_log_info("Import from bookmarks.html, but don't create smart bookmarks \ - if autoExportHTML is true and they are at latest version"); + do_print("Import from bookmarks.html, but don't create smart bookmarks \ + if autoExportHTML is true and they are at latest version"); remove_all_bookmarks(); // Sanity check: we should not have any bookmark on the toolbar. @@ -154,7 +154,7 @@ function waitForImportAndSmartBookmarks(aCallback) { run_next_test(); }); // Force nsBrowserGlue::_initPlaces() - do_log_info("Simulate Places init"); + do_print("Simulate Places init"); bg.QueryInterface(Ci.nsIObserver).observe(null, TOPIC_BROWSERGLUE_TEST, TOPICDATA_FORCE_PLACES_INIT); @@ -162,8 +162,8 @@ function waitForImportAndSmartBookmarks(aCallback) { function test_import_autoExport_oldSmartBookmarks() { - do_log_info("Import from bookmarks.html, and create smart bookmarks if \ - autoExportHTML is true and they are not at latest version."); + do_print("Import from bookmarks.html, and create smart bookmarks if \ + autoExportHTML is true and they are not at latest version."); remove_all_bookmarks(); // Sanity check: we should not have any bookmark on the toolbar. @@ -190,7 +190,7 @@ function waitForImportAndSmartBookmarks(aCallback) { run_next_test(); }); // Force nsBrowserGlue::_initPlaces() - do_log_info("Simulate Places init"); + do_print("Simulate Places init"); bg.QueryInterface(Ci.nsIObserver).observe(null, TOPIC_BROWSERGLUE_TEST, TOPICDATA_FORCE_PLACES_INIT); @@ -198,8 +198,8 @@ function waitForImportAndSmartBookmarks(aCallback) { function test_restore() { - do_log_info("restore from default bookmarks.html if \ - restore_default_bookmarks is true."); + do_print("restore from default bookmarks.html if \ + restore_default_bookmarks is true."); remove_all_bookmarks(); // Sanity check: we should not have any bookmark on the toolbar. @@ -222,7 +222,7 @@ function waitForImportAndSmartBookmarks(aCallback) { run_next_test(); }); // Force nsBrowserGlue::_initPlaces() - do_log_info("Simulate Places init"); + do_print("Simulate Places init"); bg.QueryInterface(Ci.nsIObserver).observe(null, TOPIC_BROWSERGLUE_TEST, TOPICDATA_FORCE_PLACES_INIT); @@ -231,8 +231,8 @@ function waitForImportAndSmartBookmarks(aCallback) { function test_restore_import() { - do_log_info("setting both importBookmarksHTML and \ - restore_default_bookmarks should restore defaults."); + do_print("setting both importBookmarksHTML and \ + restore_default_bookmarks should restore defaults."); remove_all_bookmarks(); // Sanity check: we should not have any bookmark on the toolbar. @@ -257,7 +257,7 @@ function waitForImportAndSmartBookmarks(aCallback) { run_next_test(); }); // Force nsBrowserGlue::_initPlaces() - do_log_info("Simulate Places init"); + do_print("Simulate Places init"); bg.QueryInterface(Ci.nsIObserver).observe(null, TOPIC_BROWSERGLUE_TEST, TOPICDATA_FORCE_PLACES_INIT); diff --git a/browser/components/places/tests/unit/test_browserGlue_urlbar_defaultbehavior_migration.js b/browser/components/places/tests/unit/test_browserGlue_urlbar_defaultbehavior_migration.js index c3ffd12beb4..581693dba33 100644 --- a/browser/components/places/tests/unit/test_browserGlue_urlbar_defaultbehavior_migration.js +++ b/browser/components/places/tests/unit/test_browserGlue_urlbar_defaultbehavior_migration.js @@ -38,7 +38,7 @@ function setupBehaviorAndMigrate(aDefaultBehavior, aAutocompleteEnabled = true) }; add_task(function*() { - do_log_info("Migrate default.behavior = 0"); + do_print("Migrate default.behavior = 0"); setupBehaviorAndMigrate(0); Assert.ok(gGetBoolPref("browser.urlbar.suggest.history"), @@ -52,7 +52,7 @@ add_task(function*() { }); add_task(function*() { - do_log_info("Migrate default.behavior = 1"); + do_print("Migrate default.behavior = 1"); setupBehaviorAndMigrate(1); Assert.ok(gGetBoolPref("browser.urlbar.suggest.history"), @@ -66,7 +66,7 @@ add_task(function*() { }); add_task(function*() { - do_log_info("Migrate default.behavior = 2"); + do_print("Migrate default.behavior = 2"); setupBehaviorAndMigrate(2); Assert.equal(gGetBoolPref("browser.urlbar.suggest.history"), false, @@ -80,7 +80,7 @@ add_task(function*() { }); add_task(function*() { - do_log_info("Migrate default.behavior = 3"); + do_print("Migrate default.behavior = 3"); setupBehaviorAndMigrate(3); Assert.ok(gGetBoolPref("browser.urlbar.suggest.history"), @@ -94,7 +94,7 @@ add_task(function*() { }); add_task(function*() { - do_log_info("Migrate default.behavior = 19"); + do_print("Migrate default.behavior = 19"); setupBehaviorAndMigrate(19); Assert.ok(gGetBoolPref("browser.urlbar.suggest.history"), @@ -108,7 +108,7 @@ add_task(function*() { }); add_task(function*() { - do_log_info("Migrate default.behavior = 33"); + do_print("Migrate default.behavior = 33"); setupBehaviorAndMigrate(33); Assert.ok(gGetBoolPref("browser.urlbar.suggest.history"), @@ -122,7 +122,7 @@ add_task(function*() { }); add_task(function*() { - do_log_info("Migrate default.behavior = 129"); + do_print("Migrate default.behavior = 129"); setupBehaviorAndMigrate(129); Assert.ok(gGetBoolPref("browser.urlbar.suggest.history"), @@ -136,7 +136,7 @@ add_task(function*() { }); add_task(function*() { - do_log_info("Migrate default.behavior = 0, autocomplete.enabled = false"); + do_print("Migrate default.behavior = 0, autocomplete.enabled = false"); setupBehaviorAndMigrate(0, false); Assert.equal(gGetBoolPref("browser.urlbar.suggest.history"), false, diff --git a/toolkit/components/places/tests/bookmarks/test_1016953-renaming-uncompressed.js b/toolkit/components/places/tests/bookmarks/test_1016953-renaming-uncompressed.js index 1b0d6802818..6f3cc98f62e 100644 --- a/toolkit/components/places/tests/bookmarks/test_1016953-renaming-uncompressed.js +++ b/toolkit/components/places/tests/bookmarks/test_1016953-renaming-uncompressed.js @@ -36,7 +36,7 @@ add_task(function* test_same_date_same_hash() { converter.charset = "UTF-8"; let result = yield OS.File.read(mostRecentBackupFile); let jsonString = converter.convertFromByteArray(result, result.length); - do_log_info("Check is valid JSON"); + do_print("Check is valid JSON"); JSON.parse(jsonString); // Cleanup @@ -66,7 +66,7 @@ add_task(function* test_same_date_diff_hash() { converter.charset = "UTF-8"; let result = yield OS.File.read(mostRecentBackupFile, { compression: "lz4" }); let jsonString = converter.convertFromByteArray(result, result.length); - do_log_info("Check is valid JSON"); + do_print("Check is valid JSON"); JSON.parse(jsonString); // Cleanup diff --git a/toolkit/components/places/tests/bookmarks/test_992901-backup-unsorted-hierarchy.js b/toolkit/components/places/tests/bookmarks/test_992901-backup-unsorted-hierarchy.js index 04d90d66c1c..0dedf879411 100644 --- a/toolkit/components/places/tests/bookmarks/test_992901-backup-unsorted-hierarchy.js +++ b/toolkit/components/places/tests/bookmarks/test_992901-backup-unsorted-hierarchy.js @@ -30,15 +30,15 @@ add_task(function() { PlacesUtils.bookmarks.removeItem(f1); yield BookmarkJSONUtils.importFromFile((yield PlacesBackups.getMostRecentBackup()), true); - do_log_info("Checking first level"); + do_print("Checking first level"); let root = PlacesUtils.getFolderContents(PlacesUtils.unfiledBookmarksFolderId).root; let level1 = root.getChild(0); do_check_eq(level1.title, "f1"); - do_log_info("Checking second level"); + do_print("Checking second level"); PlacesUtils.asContainer(level1).containerOpen = true let level2 = level1.getChild(0); do_check_eq(level2.title, "f2"); - do_log_info("Checking bookmark"); + do_print("Checking bookmark"); PlacesUtils.asContainer(level2).containerOpen = true let bookmark = level2.getChild(0); do_check_eq(bookmark.title, "bookmark"); diff --git a/toolkit/components/places/tests/bookmarks/test_997030-bookmarks-html-encode.js b/toolkit/components/places/tests/bookmarks/test_997030-bookmarks-html-encode.js index 0fdb943775a..f149be4a12b 100644 --- a/toolkit/components/places/tests/bookmarks/test_997030-bookmarks-html-encode.js +++ b/toolkit/components/places/tests/bookmarks/test_997030-bookmarks-html-encode.js @@ -27,7 +27,7 @@ add_task(function() { PlacesUtils.bookmarks.removeItem(bm); yield BookmarkHTMLUtils.importFromFile(file, true); - do_log_info("Checking first level"); + do_print("Checking first level"); let root = PlacesUtils.getFolderContents(PlacesUtils.unfiledBookmarksFolderId).root; let node = root.getChild(0); do_check_eq(node.uri, uri.spec); diff --git a/toolkit/components/places/tests/bookmarks/test_async_observers.js b/toolkit/components/places/tests/bookmarks/test_async_observers.js index 2420a87a0ad..877ffdf8212 100644 --- a/toolkit/components/places/tests/bookmarks/test_async_observers.js +++ b/toolkit/components/places/tests/bookmarks/test_async_observers.js @@ -33,7 +33,7 @@ let observer = { onItemChanged: function(aItemId, aProperty, aIsAnnotation, aNewValue, aLastModified, aItemType) { - do_log_info("Check that we got the correct change information."); + do_print("Check that we got the correct change information."); do_check_neq(this.bookmarks.indexOf(aItemId), -1); if (aProperty == "favicon") { do_check_false(aIsAnnotation); @@ -57,7 +57,7 @@ let observer = { }, onItemVisited: function(aItemId, aVisitId, aTime) { - do_log_info("Check that we got the correct visit information."); + do_print("Check that we got the correct visit information."); do_check_neq(this.bookmarks.indexOf(aItemId), -1); this.observedVisitId = aVisitId; do_check_eq(aTime, NOW); diff --git a/toolkit/components/places/tests/bookmarks/test_nsINavBookmarkObserver.js b/toolkit/components/places/tests/bookmarks/test_nsINavBookmarkObserver.js index 377067aa434..428420c8d8b 100644 --- a/toolkit/components/places/tests/bookmarks/test_nsINavBookmarkObserver.js +++ b/toolkit/components/places/tests/bookmarks/test_nsINavBookmarkObserver.js @@ -11,7 +11,7 @@ let gBookmarksObserver = { let args = this.expected.shift().args; do_check_eq(aArguments.length, args.length); for (let i = 0; i < aArguments.length; i++) { - do_log_info(aMethodName + "(args[" + i + "]: " + args[i].name + ")"); + do_print(aMethodName + "(args[" + i + "]: " + args[i].name + ")"); do_check_true(args[i].check(aArguments[i])); } diff --git a/toolkit/components/places/tests/favicons/test_replaceFaviconData.js b/toolkit/components/places/tests/favicons/test_replaceFaviconData.js index 24148ca3ce9..eae3cb497c9 100644 --- a/toolkit/components/places/tests/favicons/test_replaceFaviconData.js +++ b/toolkit/components/places/tests/favicons/test_replaceFaviconData.js @@ -59,7 +59,7 @@ function run_test() { }; add_task(function test_replaceFaviconData_validHistoryURI() { - do_log_info("test replaceFaviconData for valid history uri"); + do_print("test replaceFaviconData for valid history uri"); let pageURI = uri("http://test1.bar/"); yield promiseAddVisits(pageURI); @@ -87,7 +87,7 @@ add_task(function test_replaceFaviconData_validHistoryURI() { }); add_task(function test_replaceFaviconData_overrideDefaultFavicon() { - do_log_info("test replaceFaviconData to override a later setAndFetchFaviconForPage"); + do_print("test replaceFaviconData to override a later setAndFetchFaviconForPage"); let pageURI = uri("http://test2.bar/"); yield promiseAddVisits(pageURI); @@ -119,7 +119,7 @@ add_task(function test_replaceFaviconData_overrideDefaultFavicon() { }); add_task(function test_replaceFaviconData_replaceExisting() { - do_log_info("test replaceFaviconData to override a previous setAndFetchFaviconForPage"); + do_print("test replaceFaviconData to override a previous setAndFetchFaviconForPage"); let pageURI = uri("http://test3.bar"); yield promiseAddVisits(pageURI); @@ -156,7 +156,7 @@ add_task(function test_replaceFaviconData_replaceExisting() { }); add_task(function test_replaceFaviconData_unrelatedReplace() { - do_log_info("test replaceFaviconData to not make unrelated changes"); + do_print("test replaceFaviconData to not make unrelated changes"); let pageURI = uri("http://test4.bar/"); yield promiseAddVisits(pageURI); @@ -188,7 +188,7 @@ add_task(function test_replaceFaviconData_unrelatedReplace() { }); add_task(function test_replaceFaviconData_badInputs() { - do_log_info("test replaceFaviconData to throw on bad inputs"); + do_print("test replaceFaviconData to throw on bad inputs"); let favicon = createFavicon("favicon8.png"); @@ -228,7 +228,7 @@ add_task(function test_replaceFaviconData_badInputs() { }); add_task(function test_replaceFaviconData_twiceReplace() { - do_log_info("test replaceFaviconData on multiple replacements"); + do_print("test replaceFaviconData on multiple replacements"); let pageURI = uri("http://test5.bar/"); yield promiseAddVisits(pageURI); diff --git a/toolkit/components/places/tests/favicons/test_replaceFaviconDataFromDataURL.js b/toolkit/components/places/tests/favicons/test_replaceFaviconDataFromDataURL.js index 5d1d1d39571..6dae7e751c7 100644 --- a/toolkit/components/places/tests/favicons/test_replaceFaviconDataFromDataURL.js +++ b/toolkit/components/places/tests/favicons/test_replaceFaviconDataFromDataURL.js @@ -63,7 +63,7 @@ function run_test() { }; add_task(function test_replaceFaviconDataFromDataURL_validHistoryURI() { - do_log_info("test replaceFaviconDataFromDataURL for valid history uri"); + do_print("test replaceFaviconDataFromDataURL for valid history uri"); let pageURI = uri("http://test1.bar/"); yield promiseAddVisits(pageURI); @@ -89,7 +89,7 @@ add_task(function test_replaceFaviconDataFromDataURL_validHistoryURI() { }); add_task(function test_replaceFaviconDataFromDataURL_overrideDefaultFavicon() { - do_log_info("test replaceFaviconDataFromDataURL to override a later setAndFetchFaviconForPage"); + do_print("test replaceFaviconDataFromDataURL to override a later setAndFetchFaviconForPage"); let pageURI = uri("http://test2.bar/"); yield promiseAddVisits(pageURI); @@ -119,7 +119,7 @@ add_task(function test_replaceFaviconDataFromDataURL_overrideDefaultFavicon() { }); add_task(function test_replaceFaviconDataFromDataURL_replaceExisting() { - do_log_info("test replaceFaviconDataFromDataURL to override a previous setAndFetchFaviconForPage"); + do_print("test replaceFaviconDataFromDataURL to override a previous setAndFetchFaviconForPage"); let pageURI = uri("http://test3.bar"); yield promiseAddVisits(pageURI); @@ -152,7 +152,7 @@ add_task(function test_replaceFaviconDataFromDataURL_replaceExisting() { }); add_task(function test_replaceFaviconDataFromDataURL_unrelatedReplace() { - do_log_info("test replaceFaviconDataFromDataURL to not make unrelated changes"); + do_print("test replaceFaviconDataFromDataURL to not make unrelated changes"); let pageURI = uri("http://test4.bar/"); yield promiseAddVisits(pageURI); @@ -182,7 +182,7 @@ add_task(function test_replaceFaviconDataFromDataURL_unrelatedReplace() { }); add_task(function test_replaceFaviconDataFromDataURL_badInputs() { - do_log_info("test replaceFaviconDataFromDataURL to throw on bad inputs"); + do_print("test replaceFaviconDataFromDataURL to throw on bad inputs"); let favicon = createFavicon("favicon8.png"); @@ -210,7 +210,7 @@ add_task(function test_replaceFaviconDataFromDataURL_badInputs() { }); add_task(function test_replaceFaviconDataFromDataURL_twiceReplace() { - do_log_info("test replaceFaviconDataFromDataURL on multiple replacements"); + do_print("test replaceFaviconDataFromDataURL on multiple replacements"); let pageURI = uri("http://test5.bar/"); yield promiseAddVisits(pageURI); @@ -241,7 +241,7 @@ add_task(function test_replaceFaviconDataFromDataURL_twiceReplace() { }); add_task(function test_replaceFaviconDataFromDataURL_afterRegularAssign() { - do_log_info("test replaceFaviconDataFromDataURL after replaceFaviconData"); + do_print("test replaceFaviconDataFromDataURL after replaceFaviconData"); let pageURI = uri("http://test6.bar/"); yield promiseAddVisits(pageURI); @@ -274,7 +274,7 @@ add_task(function test_replaceFaviconDataFromDataURL_afterRegularAssign() { }); add_task(function test_replaceFaviconDataFromDataURL_beforeRegularAssign() { - do_log_info("test replaceFaviconDataFromDataURL before replaceFaviconData"); + do_print("test replaceFaviconDataFromDataURL before replaceFaviconData"); let pageURI = uri("http://test7.bar/"); yield promiseAddVisits(pageURI); diff --git a/toolkit/components/places/tests/head_common.js b/toolkit/components/places/tests/head_common.js index a1b31a8fbb1..f292b89a5ed 100644 --- a/toolkit/components/places/tests/head_common.js +++ b/toolkit/components/places/tests/head_common.js @@ -760,17 +760,6 @@ function do_check_guid_for_bookmark(aId, } } -/** - * Logs info to the console in the standard way (includes the filename). - * - * @param aMessage - * The message to log to the console. - */ -function do_log_info(aMessage) -{ - print("TEST-INFO | " + _TEST_FILE + " | " + aMessage); -} - /** * Compares 2 arrays returning whether they contains the same elements. * diff --git a/toolkit/components/places/tests/inline/head_autocomplete.js b/toolkit/components/places/tests/inline/head_autocomplete.js index 13895cef924..7dbbe259070 100644 --- a/toolkit/components/places/tests/inline/head_autocomplete.js +++ b/toolkit/components/places/tests/inline/head_autocomplete.js @@ -139,7 +139,7 @@ function ensure_results(aSearchString, aExpectedValue) { waitForCleanup(run_next_test); }; - do_log_info("Searching for: '" + aSearchString + "'"); + do_print("Searching for: '" + aSearchString + "'"); controller.startSearch(aSearchString); } @@ -153,7 +153,7 @@ function run_test() { gAutoCompleteTests.forEach(function (testData) { let [description, searchString, expectedValue, setupFunc] = testData; add_test(function () { - do_log_info(description); + do_print(description); Services.prefs.setBoolPref("browser.urlbar.autocomplete.enabled", true); Services.prefs.setBoolPref("browser.urlbar.autoFill", true); Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", false); diff --git a/toolkit/components/places/tests/network/test_history_redirects.js b/toolkit/components/places/tests/network/test_history_redirects.js index 56c82701f77..13717fb82bf 100644 --- a/toolkit/components/places/tests/network/test_history_redirects.js +++ b/toolkit/components/places/tests/network/test_history_redirects.js @@ -92,8 +92,8 @@ function continue_test() { try { while(stmt.executeStep()) { let comparator = EXPECTED.shift(); - do_log_info("Checking that '" + comparator.url + - "' was entered into the DB correctly"); + do_print("Checking that '" + comparator.url + + "' was entered into the DB correctly"); do_check_eq(stmt.row.id, comparator.id); do_check_eq(stmt.row.url, comparator.url); do_check_eq(stmt.row.from_visit, comparator.from_visit); @@ -155,7 +155,7 @@ ChannelListener.prototype = { }, onStartRequest: function(request, context) { - do_log_info("onStartRequest"); + do_print("onStartRequest"); this._got_onstartrequest = true; }, @@ -164,7 +164,7 @@ ChannelListener.prototype = { }, onStopRequest: function(request, context, status) { - do_log_info("onStopRequest"); + do_print("onStopRequest"); this._got_onstoprequest++; let success = Components.isSuccessCode(status); do_check_true(success); @@ -177,7 +177,7 @@ ChannelListener.prototype = { // nsIChannelEventSink asyncOnChannelRedirect: function (aOldChannel, aNewChannel, aFlags, callback) { - do_log_info("onChannelRedirect"); + do_print("onChannelRedirect"); this._got_onchannelredirect = true; callback.onRedirectVerifyCallback(Components.results.NS_OK); }, diff --git a/toolkit/components/places/tests/queries/test_tags.js b/toolkit/components/places/tests/queries/test_tags.js index 7be4f35f9c6..85d99de7765 100644 --- a/toolkit/components/places/tests/queries/test_tags.js +++ b/toolkit/components/places/tests/queries/test_tags.js @@ -13,29 +13,29 @@ function tags_getter_setter() { - do_log_info("Tags getter/setter should work correctly"); - do_log_info("Without setting tags, tags getter should return empty array"); + do_print("Tags getter/setter should work correctly"); + do_print("Without setting tags, tags getter should return empty array"); var [query, dummy] = makeQuery(); do_check_eq(query.tags.length, 0); - do_log_info("Setting tags to an empty array, tags getter should return "+ - "empty array"); + do_print("Setting tags to an empty array, tags getter should return "+ + "empty array"); [query, dummy] = makeQuery([]); do_check_eq(query.tags.length, 0); - do_log_info("Setting a few tags, tags getter should return correct array"); + do_print("Setting a few tags, tags getter should return correct array"); var tags = ["bar", "baz", "foo"]; [query, dummy] = makeQuery(tags); setsAreEqual(query.tags, tags, true); - do_log_info("Setting some dupe tags, tags getter return unique tags"); + do_print("Setting some dupe tags, tags getter return unique tags"); [query, dummy] = makeQuery(["foo", "foo", "bar", "foo", "baz", "bar"]); setsAreEqual(query.tags, ["bar", "baz", "foo"], true); }, function invalid_setter_calls() { - do_log_info("Invalid calls to tags setter should fail"); + do_print("Invalid calls to tags setter should fail"); try { var query = PlacesUtils.history.getNewQuery(); query.tags = null; @@ -105,19 +105,19 @@ function not_setting_tags() { - do_log_info("Not setting tags at all should not affect query URI"); + do_print("Not setting tags at all should not affect query URI"); checkQueryURI(); }, function empty_array_tags() { - do_log_info("Setting tags with an empty array should not affect query URI"); + do_print("Setting tags with an empty array should not affect query URI"); checkQueryURI([]); }, function set_tags() { - do_log_info("Setting some tags should result in correct query URI"); + do_print("Setting some tags should result in correct query URI"); checkQueryURI([ "foo", "七難", @@ -133,22 +133,22 @@ function no_tags_tagsAreNot() { - do_log_info("Not setting tags at all but setting tagsAreNot should " + - "affect query URI"); + do_print("Not setting tags at all but setting tagsAreNot should " + + "affect query URI"); checkQueryURI(null, true); }, function empty_array_tags_tagsAreNot() { - do_log_info("Setting tags with an empty array and setting tagsAreNot " + - "should affect query URI"); + do_print("Setting tags with an empty array and setting tagsAreNot " + + "should affect query URI"); checkQueryURI([], true); }, function () { - do_log_info("Setting some tags and setting tagsAreNot should result in " + - "correct query URI"); + do_print("Setting some tags and setting tagsAreNot should result in " + + "correct query URI"); checkQueryURI([ "foo", "七難", @@ -164,8 +164,8 @@ function tag_to_uri() { - do_log_info("Querying history on tag associated with a URI should return " + - "that URI"); + do_print("Querying history on tag associated with a URI should return " + + "that URI"); yield task_doWithVisit(["foo", "bar", "baz"], function (aURI) { var [query, opts] = makeQuery(["foo"]); executeAndCheckQueryResults(query, opts, [aURI.spec]); @@ -178,8 +178,8 @@ function tags_to_uri() { - do_log_info("Querying history on many tags associated with a URI should " + - "return that URI"); + do_print("Querying history on many tags associated with a URI should " + + "return that URI"); yield task_doWithVisit(["foo", "bar", "baz"], function (aURI) { var [query, opts] = makeQuery(["foo", "bar"]); executeAndCheckQueryResults(query, opts, [aURI.spec]); @@ -194,8 +194,8 @@ function repeated_tag() { - do_log_info("Specifying the same tag multiple times in a history query " + - "should not matter"); + do_print("Specifying the same tag multiple times in a history query " + + "should not matter"); yield task_doWithVisit(["foo", "bar", "baz"], function (aURI) { var [query, opts] = makeQuery(["foo", "foo"]); executeAndCheckQueryResults(query, opts, [aURI.spec]); @@ -206,8 +206,8 @@ function many_tags_no_uri() { - do_log_info("Querying history on many tags associated with a URI and " + - "tags not associated with that URI should not return that URI"); + do_print("Querying history on many tags associated with a URI and " + + "tags not associated with that URI should not return that URI"); yield task_doWithVisit(["foo", "bar", "baz"], function (aURI) { var [query, opts] = makeQuery(["foo", "bogus"]); executeAndCheckQueryResults(query, opts, []); @@ -220,7 +220,7 @@ function nonexistent_tags() { - do_log_info("Querying history on nonexistent tags should return no results"); + do_print("Querying history on nonexistent tags should return no results"); yield task_doWithVisit(["foo", "bar", "baz"], function (aURI) { var [query, opts] = makeQuery(["bogus"]); executeAndCheckQueryResults(query, opts, []); @@ -231,8 +231,8 @@ function tag_to_bookmark() { - do_log_info("Querying bookmarks on tag associated with a URI should " + - "return that URI"); + do_print("Querying bookmarks on tag associated with a URI should " + + "return that URI"); yield task_doWithBookmark(["foo", "bar", "baz"], function (aURI) { var [query, opts] = makeQuery(["foo"]); opts.queryType = opts.QUERY_TYPE_BOOKMARKS; @@ -248,8 +248,8 @@ function many_tags_to_bookmark() { - do_log_info("Querying bookmarks on many tags associated with a URI " + - "should return that URI"); + do_print("Querying bookmarks on many tags associated with a URI " + + "should return that URI"); yield task_doWithBookmark(["foo", "bar", "baz"], function (aURI) { var [query, opts] = makeQuery(["foo", "bar"]); opts.queryType = opts.QUERY_TYPE_BOOKMARKS; @@ -268,8 +268,8 @@ function repeated_tag_to_bookmarks() { - do_log_info("Specifying the same tag multiple times in a bookmark query " + - "should not matter"); + do_print("Specifying the same tag multiple times in a bookmark query " + + "should not matter"); yield task_doWithBookmark(["foo", "bar", "baz"], function (aURI) { var [query, opts] = makeQuery(["foo", "foo"]); opts.queryType = opts.QUERY_TYPE_BOOKMARKS; @@ -282,8 +282,8 @@ function many_tags_no_bookmark() { - do_log_info("Querying bookmarks on many tags associated with a URI and " + - "tags not associated with that URI should not return that URI"); + do_print("Querying bookmarks on many tags associated with a URI and " + + "tags not associated with that URI should not return that URI"); yield task_doWithBookmark(["foo", "bar", "baz"], function (aURI) { var [query, opts] = makeQuery(["foo", "bogus"]); opts.queryType = opts.QUERY_TYPE_BOOKMARKS; @@ -299,7 +299,7 @@ function nonexistent_tags_bookmark() { - do_log_info("Querying bookmarks on nonexistent tag should return no results"); + do_print("Querying bookmarks on nonexistent tag should return no results"); yield task_doWithBookmark(["foo", "bar", "baz"], function (aURI) { var [query, opts] = makeQuery(["bogus"]); opts.queryType = opts.QUERY_TYPE_BOOKMARKS; @@ -312,14 +312,14 @@ function tagsAreNot_history() { - do_log_info("Querying history using tagsAreNot should work correctly"); + do_print("Querying history using tagsAreNot should work correctly"); var urisAndTags = { "http://example.com/1": ["foo", "bar"], "http://example.com/2": ["baz", "qux"], "http://example.com/3": null }; - do_log_info("Add visits and tag the URIs"); + do_print("Add visits and tag the URIs"); for (let [pURI, tags] in Iterator(urisAndTags)) { let nsiuri = uri(pURI); yield promiseAddVisits(nsiuri); @@ -327,27 +327,27 @@ PlacesUtils.tagging.tagURI(nsiuri, tags); } - do_log_info(' Querying for "foo" should match only /2 and /3'); + do_print(' Querying for "foo" should match only /2 and /3'); var [query, opts] = makeQuery(["foo"], true); queryResultsAre(PlacesUtils.history.executeQuery(query, opts).root, ["http://example.com/2", "http://example.com/3"]); - do_log_info(' Querying for "foo" and "bar" should match only /2 and /3'); + do_print(' Querying for "foo" and "bar" should match only /2 and /3'); [query, opts] = makeQuery(["foo", "bar"], true); queryResultsAre(PlacesUtils.history.executeQuery(query, opts).root, ["http://example.com/2", "http://example.com/3"]); - do_log_info(' Querying for "foo" and "bogus" should match only /2 and /3'); + do_print(' Querying for "foo" and "bogus" should match only /2 and /3'); [query, opts] = makeQuery(["foo", "bogus"], true); queryResultsAre(PlacesUtils.history.executeQuery(query, opts).root, ["http://example.com/2", "http://example.com/3"]); - do_log_info(' Querying for "foo" and "baz" should match only /3'); + do_print(' Querying for "foo" and "baz" should match only /3'); [query, opts] = makeQuery(["foo", "baz"], true); queryResultsAre(PlacesUtils.history.executeQuery(query, opts).root, ["http://example.com/3"]); - do_log_info(' Querying for "bogus" should match all'); + do_print(' Querying for "bogus" should match all'); [query, opts] = makeQuery(["bogus"], true); queryResultsAre(PlacesUtils.history.executeQuery(query, opts).root, ["http://example.com/1", @@ -365,14 +365,14 @@ function tagsAreNot_bookmarks() { - do_log_info("Querying bookmarks using tagsAreNot should work correctly"); + do_print("Querying bookmarks using tagsAreNot should work correctly"); var urisAndTags = { "http://example.com/1": ["foo", "bar"], "http://example.com/2": ["baz", "qux"], "http://example.com/3": null }; - do_log_info("Add bookmarks and tag the URIs"); + do_print("Add bookmarks and tag the URIs"); for (let [pURI, tags] in Iterator(urisAndTags)) { let nsiuri = uri(pURI); addBookmark(nsiuri); @@ -380,31 +380,31 @@ PlacesUtils.tagging.tagURI(nsiuri, tags); } - do_log_info(' Querying for "foo" should match only /2 and /3'); + do_print(' Querying for "foo" should match only /2 and /3'); var [query, opts] = makeQuery(["foo"], true); opts.queryType = opts.QUERY_TYPE_BOOKMARKS; queryResultsAre(PlacesUtils.history.executeQuery(query, opts).root, ["http://example.com/2", "http://example.com/3"]); - do_log_info(' Querying for "foo" and "bar" should match only /2 and /3'); + do_print(' Querying for "foo" and "bar" should match only /2 and /3'); [query, opts] = makeQuery(["foo", "bar"], true); opts.queryType = opts.QUERY_TYPE_BOOKMARKS; queryResultsAre(PlacesUtils.history.executeQuery(query, opts).root, ["http://example.com/2", "http://example.com/3"]); - do_log_info(' Querying for "foo" and "bogus" should match only /2 and /3'); + do_print(' Querying for "foo" and "bogus" should match only /2 and /3'); [query, opts] = makeQuery(["foo", "bogus"], true); opts.queryType = opts.QUERY_TYPE_BOOKMARKS; queryResultsAre(PlacesUtils.history.executeQuery(query, opts).root, ["http://example.com/2", "http://example.com/3"]); - do_log_info(' Querying for "foo" and "baz" should match only /3'); + do_print(' Querying for "foo" and "baz" should match only /3'); [query, opts] = makeQuery(["foo", "baz"], true); opts.queryType = opts.QUERY_TYPE_BOOKMARKS; queryResultsAre(PlacesUtils.history.executeQuery(query, opts).root, ["http://example.com/3"]); - do_log_info(' Querying for "bogus" should match all'); + do_print(' Querying for "bogus" should match all'); [query, opts] = makeQuery(["bogus"], true); opts.queryType = opts.QUERY_TYPE_BOOKMARKS; queryResultsAre(PlacesUtils.history.executeQuery(query, opts).root, @@ -422,16 +422,16 @@ }, function duplicate_tags() { - do_log_info("Duplicate existing tags (i.e., multiple tag folders with " + - "same name) should not throw off query results"); + do_print("Duplicate existing tags (i.e., multiple tag folders with " + + "same name) should not throw off query results"); var tagName = "foo"; - do_log_info("Add bookmark and tag it normally"); + do_print("Add bookmark and tag it normally"); addBookmark(TEST_URI); PlacesUtils.tagging.tagURI(TEST_URI, [tagName]); - do_log_info("Manually create tag folder with same name as tag and insert " + - "bookmark"); + do_print("Manually create tag folder with same name as tag and insert " + + "bookmark"); var dupTagId = PlacesUtils.bookmarks.createFolder(PlacesUtils.tagsFolderId, tagName, Ci.nsINavBookmarksService.DEFAULT_INDEX); @@ -442,7 +442,7 @@ "title"); do_check_true(bmId > 0); - do_log_info("Querying for tag should match URI"); + do_print("Querying for tag should match URI"); var [query, opts] = makeQuery([tagName]); opts.queryType = opts.QUERY_TYPE_BOOKMARKS; queryResultsAre(PlacesUtils.history.executeQuery(query, opts).root, [TEST_URI.spec]); @@ -453,21 +453,21 @@ function folder_named_as_tag() { - do_log_info("Regular folders with the same name as tag should not throw " + - "off query results"); + do_print("Regular folders with the same name as tag should not throw " + + "off query results"); var tagName = "foo"; - do_log_info("Add bookmark and tag it"); + do_print("Add bookmark and tag it"); addBookmark(TEST_URI); PlacesUtils.tagging.tagURI(TEST_URI, [tagName]); - do_log_info("Create folder with same name as tag"); + do_print("Create folder with same name as tag"); var folderId = PlacesUtils.bookmarks.createFolder(PlacesUtils.unfiledBookmarksFolderId, tagName, Ci.nsINavBookmarksService.DEFAULT_INDEX); do_check_true(folderId > 0); - do_log_info("Querying for tag should match URI"); + do_print("Querying for tag should match URI"); var [query, opts] = makeQuery([tagName]); opts.queryType = opts.QUERY_TYPE_BOOKMARKS; queryResultsAre(PlacesUtils.history.executeQuery(query, opts).root, [TEST_URI.spec]); @@ -477,7 +477,7 @@ }, function ORed_queries() { - do_log_info("Multiple queries ORed together should work"); + do_print("Multiple queries ORed together should work"); var urisAndTags = { "http://example.com/1": [], "http://example.com/2": [] @@ -490,7 +490,7 @@ urisAndTags["http://example.com/2"].push("/2 tag " + i); } - do_log_info("Add visits and tag the URIs"); + do_print("Add visits and tag the URIs"); for (let [pURI, tags] in Iterator(urisAndTags)) { let nsiuri = uri(pURI); yield promiseAddVisits(nsiuri); @@ -498,40 +498,40 @@ PlacesUtils.tagging.tagURI(nsiuri, tags); } - do_log_info("Query for /1 OR query for /2 should match both /1 and /2"); + do_print("Query for /1 OR query for /2 should match both /1 and /2"); var [query1, opts] = makeQuery(urisAndTags["http://example.com/1"]); var [query2, dummy] = makeQuery(urisAndTags["http://example.com/2"]); var root = PlacesUtils.history.executeQueries([query1, query2], 2, opts).root; queryResultsAre(root, ["http://example.com/1", "http://example.com/2"]); - do_log_info("Query for /1 OR query on bogus tag should match only /1"); + do_print("Query for /1 OR query on bogus tag should match only /1"); [query1, opts] = makeQuery(urisAndTags["http://example.com/1"]); [query2, dummy] = makeQuery(["bogus"]); root = PlacesUtils.history.executeQueries([query1, query2], 2, opts).root; queryResultsAre(root, ["http://example.com/1"]); - do_log_info("Query for /1 OR query for /1 should match only /1"); + do_print("Query for /1 OR query for /1 should match only /1"); [query1, opts] = makeQuery(urisAndTags["http://example.com/1"]); [query2, dummy] = makeQuery(urisAndTags["http://example.com/1"]); root = PlacesUtils.history.executeQueries([query1, query2], 2, opts).root; queryResultsAre(root, ["http://example.com/1"]); - do_log_info("Query for /1 with tagsAreNot OR query for /2 with tagsAreNot " + - "should match both /1 and /2"); + do_print("Query for /1 with tagsAreNot OR query for /2 with tagsAreNot " + + "should match both /1 and /2"); [query1, opts] = makeQuery(urisAndTags["http://example.com/1"], true); [query2, dummy] = makeQuery(urisAndTags["http://example.com/2"], true); root = PlacesUtils.history.executeQueries([query1, query2], 2, opts).root; queryResultsAre(root, ["http://example.com/1", "http://example.com/2"]); - do_log_info("Query for /1 OR query for /2 with tagsAreNot should match " + - "only /1"); + do_print("Query for /1 OR query for /2 with tagsAreNot should match " + + "only /1"); [query1, opts] = makeQuery(urisAndTags["http://example.com/1"]); [query2, dummy] = makeQuery(urisAndTags["http://example.com/2"], true); root = PlacesUtils.history.executeQueries([query1, query2], 2, opts).root; queryResultsAre(root, ["http://example.com/1"]); - do_log_info("Query for /1 OR query for /1 with tagsAreNot should match " + - "both URIs"); + do_print("Query for /1 OR query for /1 with tagsAreNot should match " + + "both URIs"); [query1, opts] = makeQuery(urisAndTags["http://example.com/1"]); [query2, dummy] = makeQuery(urisAndTags["http://example.com/1"], true); root = PlacesUtils.history.executeQueries([query1, query2], 2, opts).root; @@ -568,7 +568,7 @@ function addBookmark(aURI) { aURI, Ci.nsINavBookmarksService.DEFAULT_INDEX, aURI.spec); - do_log_info("Sanity check: insertBookmark should not fail"); + do_print("Sanity check: insertBookmark should not fail"); do_check_true(bmId > 0); } @@ -596,7 +596,7 @@ function checkQueryURI(aTags, aTagsAreNot) { var expURI = "place:" + pairs.join("&"); var [query, opts] = makeQuery(aTags, aTagsAreNot); var actualURI = queryURI(query, opts); - do_log_info("Query URI should be what we expect for the given tags"); + do_print("Query URI should be what we expect for the given tags"); do_check_eq(actualURI, expURI); } @@ -683,7 +683,7 @@ function executeAndCheckQueryResults(aQuery, aQueryOpts, aExpectedURIs) { */ function makeQuery(aTags, aTagsAreNot) { aTagsAreNot = !!aTagsAreNot; - do_log_info("Making a query " + + do_print("Making a query " + (aTags ? "with tags " + aTags.toSource() : "without calling setTags() at all") + @@ -701,7 +701,7 @@ function makeQuery(aTags, aTagsAreNot) { uniqueTags.sort(); } - do_log_info("Made query should be correct for tags and tagsAreNot"); + do_print("Made query should be correct for tags and tagsAreNot"); if (uniqueTags) setsAreEqual(query.tags, uniqueTags, true); var expCount = uniqueTags ? uniqueTags.length : 0; diff --git a/toolkit/components/places/tests/unifiedcomplete/head_autocomplete.js b/toolkit/components/places/tests/unifiedcomplete/head_autocomplete.js index 5aed5ee6cb7..3613f9f0630 100644 --- a/toolkit/components/places/tests/unifiedcomplete/head_autocomplete.js +++ b/toolkit/components/places/tests/unifiedcomplete/head_autocomplete.js @@ -122,12 +122,12 @@ function* check_autocomplete(test) { let numSearchesStarted = 0; input.onSearchBegin = () => { - do_log_info("onSearchBegin received"); + do_print("onSearchBegin received"); numSearchesStarted++; }; let deferred = Promise.defer(); input.onSearchComplete = () => { - do_log_info("onSearchComplete received"); + do_print("onSearchComplete received"); deferred.resolve(); } @@ -136,7 +136,7 @@ function* check_autocomplete(test) { controller.startSearch(test.incompleteSearch); expectedSearches++; } - do_log_info("Searching for: '" + test.search + "'"); + do_print("Searching for: '" + test.search + "'"); controller.startSearch(test.search); yield deferred.promise; @@ -150,7 +150,7 @@ function* check_autocomplete(test) { for (let i = 0; i < controller.matchCount; i++) { let value = controller.getValueAt(i); let comment = controller.getCommentAt(i); - do_log_info("Looking for '" + value + "', '" + comment + "' in expected results..."); + do_print("Looking for '" + value + "', '" + comment + "' in expected results..."); let j; for (j = 0; j < matches.length; j++) { // Skip processed expected results @@ -167,10 +167,10 @@ function* check_autocomplete(test) { else style = ["favicon"]; - do_log_info("Checking against expected '" + uri.spec + "', '" + title + "'..."); + do_print("Checking against expected '" + uri.spec + "', '" + title + "'..."); // Got a match on both uri and title? if (stripPrefix(uri.spec) == stripPrefix(value) && title == comment) { - do_log_info("Got a match at index " + j + "!"); + do_print("Got a match at index " + j + "!"); let actualStyle = controller.getStyleAt(i).split(/\s+/).sort(); if (style) Assert.equal(actualStyle.toString(), style.toString(), "Match should have expected style"); @@ -254,7 +254,7 @@ function changeRestrict(aType, aChar) { else branch += "restrict."; - do_log_info("changing restrict for " + aType + " to '" + aChar + "'"); + do_print("changing restrict for " + aType + " to '" + aChar + "'"); Services.prefs.setCharPref(branch + aType, aChar); } diff --git a/toolkit/components/places/tests/unifiedcomplete/test_416211.js b/toolkit/components/places/tests/unifiedcomplete/test_416211.js index 0592cd83b0a..014563f6b21 100644 --- a/toolkit/components/places/tests/unifiedcomplete/test_416211.js +++ b/toolkit/components/places/tests/unifiedcomplete/test_416211.js @@ -8,7 +8,7 @@ */ add_task(function* test_tag_match_has_bookmark_title() { - do_log_info("Make sure the tag match gives the bookmark title"); + do_print("Make sure the tag match gives the bookmark title"); let uri = NetUtil.newURI("http://theuri/"); yield promiseAddVisits({ uri: uri, title: "Page title" }); addBookmark({ uri: uri, diff --git a/toolkit/components/places/tests/unifiedcomplete/test_416214.js b/toolkit/components/places/tests/unifiedcomplete/test_416214.js index 8fe1267d7a0..35030f16031 100644 --- a/toolkit/components/places/tests/unifiedcomplete/test_416214.js +++ b/toolkit/components/places/tests/unifiedcomplete/test_416214.js @@ -15,7 +15,7 @@ */ add_task(function* test_tag_match_url() { - do_log_info("Make sure tag matches return the right url as well as '+' remain escaped"); + do_print("Make sure tag matches return the right url as well as '+' remain escaped"); let uri1 = NetUtil.newURI("http://escaped/ユニコード"); let uri2 = NetUtil.newURI("http://asciiescaped/blocking-firefox3%2B"); yield promiseAddVisits([ { uri: uri1, title: "title" }, diff --git a/toolkit/components/places/tests/unifiedcomplete/test_417798.js b/toolkit/components/places/tests/unifiedcomplete/test_417798.js index aa5555b5b68..4f307b589bd 100644 --- a/toolkit/components/places/tests/unifiedcomplete/test_417798.js +++ b/toolkit/components/places/tests/unifiedcomplete/test_417798.js @@ -16,32 +16,32 @@ add_task(function* test_javascript_match() { addBookmark({ uri: uri2, title: "Title with javascript:" }); - do_log_info("Match non-javascript: with plain search"); + do_print("Match non-javascript: with plain search"); yield check_autocomplete({ search: "a", matches: [ { uri: uri1, title: "Title with javascript:" } ] }); - do_log_info("Match non-javascript: with almost javascript:"); + do_print("Match non-javascript: with almost javascript:"); yield check_autocomplete({ search: "javascript", matches: [ { uri: uri1, title: "Title with javascript:" } ] }); - do_log_info("Match javascript:"); + do_print("Match javascript:"); yield check_autocomplete({ search: "javascript:", matches: [ { uri: uri1, title: "Title with javascript:" }, { uri: uri2, title: "Title with javascript:", style: [ "bookmark" ]} ] }); - do_log_info("Match nothing with non-first javascript:"); + do_print("Match nothing with non-first javascript:"); yield check_autocomplete({ search: "5 javascript:", matches: [ ] }); - do_log_info("Match javascript: with multi-word search"); + do_print("Match javascript: with multi-word search"); yield check_autocomplete({ search: "javascript: 5", matches: [ { uri: uri2, title: "Title with javascript:", style: [ "bookmark" ]} ] diff --git a/toolkit/components/places/tests/unifiedcomplete/test_418257.js b/toolkit/components/places/tests/unifiedcomplete/test_418257.js index 64bbdaf5195..b6064ee024f 100644 --- a/toolkit/components/places/tests/unifiedcomplete/test_418257.js +++ b/toolkit/components/places/tests/unifiedcomplete/test_418257.js @@ -30,31 +30,31 @@ add_task(function* test_javascript_match() { title: "tagged", tags: [ "tag1", "tag2", "tag3" ] }); - do_log_info("Make sure tags come back in the title when matching tags"); + do_print("Make sure tags come back in the title when matching tags"); yield check_autocomplete({ search: "page1 tag", matches: [ { uri: uri1, title: "tagged", tags: [ "tag1" ], style: [ "bookmark-tag" ] } ] }); - do_log_info("Check tags in title for page2"); + do_print("Check tags in title for page2"); yield check_autocomplete({ search: "page2 tag", matches: [ { uri: uri2, title: "tagged", tags: [ "tag1", "tag2" ], style: [ "bookmark-tag" ] } ] }); - do_log_info("Make sure tags appear even when not matching the tag"); + do_print("Make sure tags appear even when not matching the tag"); yield check_autocomplete({ search: "page3", matches: [ { uri: uri3, title: "tagged", tags: [ "tag1", "tag3" ], style: [ "bookmark-tag" ] } ] }); - do_log_info("Multiple tags come in commas for page4"); + do_print("Multiple tags come in commas for page4"); yield check_autocomplete({ search: "page4", matches: [ { uri: uri4, title: "tagged", tags: [ "tag1", "tag2", "tag3" ], style: [ "bookmark-tag" ] } ] }); - do_log_info("Extra test just to make sure we match the title"); + do_print("Extra test just to make sure we match the title"); yield check_autocomplete({ search: "tag2", matches: [ { uri: uri2, title: "tagged", tags: [ "tag1", "tag2" ], style: [ "bookmark-tag" ] }, diff --git a/toolkit/components/places/tests/unifiedcomplete/test_422277.js b/toolkit/components/places/tests/unifiedcomplete/test_422277.js index a738c2a82ee..b6e7b5e5f54 100644 --- a/toolkit/components/places/tests/unifiedcomplete/test_422277.js +++ b/toolkit/components/places/tests/unifiedcomplete/test_422277.js @@ -8,7 +8,7 @@ */ add_task(function* test_javascript_match() { - do_log_info("Bad escaped uri stays escaped"); + do_print("Bad escaped uri stays escaped"); let uri1 = NetUtil.newURI("http://site/%EAid"); yield promiseAddVisits([ { uri: uri1, title: "title" } ]); yield check_autocomplete({ diff --git a/toolkit/components/places/tests/unifiedcomplete/test_autoFill_default_behavior.js b/toolkit/components/places/tests/unifiedcomplete/test_autoFill_default_behavior.js index 57d6cbf26f6..57e759e17cb 100644 --- a/toolkit/components/places/tests/unifiedcomplete/test_autoFill_default_behavior.js +++ b/toolkit/components/places/tests/unifiedcomplete/test_autoFill_default_behavior.js @@ -27,7 +27,7 @@ add_task(function* test_default_behavior_host() { Services.prefs.setBoolPref("browser.urlbar.suggest.history.onlyTyped", false); Services.prefs.setBoolPref("browser.urlbar.suggest.bookmark", false); - do_log_info("Restrict history, common visit, should not autoFill"); + do_print("Restrict history, common visit, should not autoFill"); yield check_autocomplete({ search: "vi", matches: [ { uri: uri2, title: "visited" } ], @@ -35,7 +35,7 @@ add_task(function* test_default_behavior_host() { completed: "vi" }); - do_log_info("Restrict history, typed visit, should autoFill"); + do_print("Restrict history, typed visit, should autoFill"); yield check_autocomplete({ search: "ty", matches: [ { uri: uri1, title: "typed", style: [ "autofill" ] } ], @@ -44,7 +44,7 @@ add_task(function* test_default_behavior_host() { }); // Don't autoFill this one cause it's not typed. - do_log_info("Restrict history, bookmark, should not autoFill"); + do_print("Restrict history, bookmark, should not autoFill"); yield check_autocomplete({ search: "bo", matches: [ ], @@ -53,7 +53,7 @@ add_task(function* test_default_behavior_host() { }); // Note we don't show this one cause it's not typed. - do_log_info("Restrict history, typed bookmark, should autoFill"); + do_print("Restrict history, typed bookmark, should autoFill"); yield check_autocomplete({ search: "tp", matches: [ { uri: uri4, title: "tpbk", style: [ "autofill" ] } ], @@ -66,7 +66,7 @@ add_task(function* test_default_behavior_host() { // We are not restricting on typed, so we autoFill the bookmark even if we // are restricted to history. We accept that cause not doing that // would be a perf hit and the privacy implications are very weak. - do_log_info("Restrict history, bookmark, autoFill.typed = false, should autoFill"); + do_print("Restrict history, bookmark, autoFill.typed = false, should autoFill"); yield check_autocomplete({ search: "bo", matches: [ { uri: uri3, title: "bookmarked", style: [ "bookmark" ], style: [ "autofill" ] } ], @@ -74,7 +74,7 @@ add_task(function* test_default_behavior_host() { completed: "bookmarked/" }); - do_log_info("Restrict history, common visit, autoFill.typed = false, should autoFill"); + do_print("Restrict history, common visit, autoFill.typed = false, should autoFill"); yield check_autocomplete({ search: "vi", matches: [ { uri: uri2, title: "visited", style: [ "autofill" ] } ], @@ -87,7 +87,7 @@ add_task(function* test_default_behavior_host() { Services.prefs.setBoolPref("browser.urlbar.suggest.history.onlyTyped", true); // Typed behavior basically acts like history, but filters on typed. - do_log_info("Restrict typed, common visit, autoFill.typed = false, should not autoFill"); + do_print("Restrict typed, common visit, autoFill.typed = false, should not autoFill"); yield check_autocomplete({ search: "vi", matches: [ ], @@ -95,7 +95,7 @@ add_task(function* test_default_behavior_host() { completed: "vi" }); - do_log_info("Restrict typed, typed visit, autofill.typed = false, should autoFill"); + do_print("Restrict typed, typed visit, autofill.typed = false, should autoFill"); yield check_autocomplete({ search: "ty", matches: [ { uri: uri1, title: "typed", style: [ "autofill" ] } ], @@ -103,7 +103,7 @@ add_task(function* test_default_behavior_host() { completed: "typed/" }); - do_log_info("Restrict typed, bookmark, autofill.typed = false, should not autoFill"); + do_print("Restrict typed, bookmark, autofill.typed = false, should not autoFill"); yield check_autocomplete({ search: "bo", matches: [ ], @@ -111,7 +111,7 @@ add_task(function* test_default_behavior_host() { completed: "bo" }); - do_log_info("Restrict typed, typed bookmark, autofill.typed = false, should autoFill"); + do_print("Restrict typed, typed bookmark, autofill.typed = false, should autoFill"); yield check_autocomplete({ search: "tp", matches: [ { uri: uri4, title: "tpbk", style: [ "autofill" ] } ], @@ -124,7 +124,7 @@ add_task(function* test_default_behavior_host() { Services.prefs.setBoolPref("browser.urlbar.suggest.bookmark", true); Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", true); - do_log_info("Restrict bookmarks, common visit, should not autoFill"); + do_print("Restrict bookmarks, common visit, should not autoFill"); yield check_autocomplete({ search: "vi", matches: [ ], @@ -132,7 +132,7 @@ add_task(function* test_default_behavior_host() { completed: "vi" }); - do_log_info("Restrict bookmarks, typed visit, should not autoFill"); + do_print("Restrict bookmarks, typed visit, should not autoFill"); yield check_autocomplete({ search: "ty", matches: [ ], @@ -141,7 +141,7 @@ add_task(function* test_default_behavior_host() { }); // Don't autoFill this one cause it's not typed. - do_log_info("Restrict bookmarks, bookmark, should not autoFill"); + do_print("Restrict bookmarks, bookmark, should not autoFill"); yield check_autocomplete({ search: "bo", matches: [ { uri: uri3, title: "bookmarked", style: [ "bookmark" ] } ], @@ -150,7 +150,7 @@ add_task(function* test_default_behavior_host() { }); // Note we don't show this one cause it's not typed. - do_log_info("Restrict bookmarks, typed bookmark, should autoFill"); + do_print("Restrict bookmarks, typed bookmark, should autoFill"); yield check_autocomplete({ search: "tp", matches: [ { uri: uri4, title: "tpbk", style: [ "autofill" ] } ], @@ -160,7 +160,7 @@ add_task(function* test_default_behavior_host() { Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", false); - do_log_info("Restrict bookmarks, bookmark, autofill.typed = false, should autoFill"); + do_print("Restrict bookmarks, bookmark, autofill.typed = false, should autoFill"); yield check_autocomplete({ search: "bo", matches: [ { uri: uri3, title: "bookmarked", style: [ "autofill" ] } ], @@ -169,7 +169,7 @@ add_task(function* test_default_behavior_host() { }); // Don't autofill because it's a title. - do_log_info("Restrict bookmarks, title, autofill.typed = false, should not autoFill"); + do_print("Restrict bookmarks, title, autofill.typed = false, should not autoFill"); yield check_autocomplete({ search: "# ta", matches: [ ], @@ -178,7 +178,7 @@ add_task(function* test_default_behavior_host() { }); // Don't autofill because it's a tag. - do_log_info("Restrict bookmarks, tag, autofill.typed = false, should not autoFill"); + do_print("Restrict bookmarks, tag, autofill.typed = false, should not autoFill"); yield check_autocomplete({ search: "+ ta", matches: [ { uri: uri5, title: "title", tags: [ "foo" ], style: [ "tag" ] } ], @@ -210,7 +210,7 @@ add_task(function* test_default_behavior_url() { Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", true); Services.prefs.setBoolPref("browser.urlbar.autoFill.searchEngines", false); - do_log_info("URL: Restrict history, common visit, should not autoFill"); + do_print("URL: Restrict history, common visit, should not autoFill"); yield check_autocomplete({ search: "visited/v", matches: [ { uri: uri2, title: "visited" } ], @@ -218,7 +218,7 @@ add_task(function* test_default_behavior_url() { completed: "visited/v" }); - do_log_info("URL: Restrict history, typed visit, should autoFill"); + do_print("URL: Restrict history, typed visit, should autoFill"); yield check_autocomplete({ search: "typed/t", matches: [ { uri: uri1, title: "typed/ty/", style: [ "autofill" ] } ], @@ -227,7 +227,7 @@ add_task(function* test_default_behavior_url() { }); // Don't autoFill this one cause it's not typed. - do_log_info("URL: Restrict history, bookmark, should not autoFill"); + do_print("URL: Restrict history, bookmark, should not autoFill"); yield check_autocomplete({ search: "bookmarked/b", matches: [ ], @@ -236,7 +236,7 @@ add_task(function* test_default_behavior_url() { }); // Note we don't show this one cause it's not typed. - do_log_info("URL: Restrict history, typed bookmark, should autoFill"); + do_print("URL: Restrict history, typed bookmark, should autoFill"); yield check_autocomplete({ search: "tpbk/t", matches: [ { uri: uri4, title: "tpbk/tp/", style: [ "autofill" ] } ], @@ -248,7 +248,7 @@ add_task(function* test_default_behavior_url() { Services.prefs.setBoolPref("browser.urlbar.suggest.history", false); Services.prefs.setBoolPref("browser.urlbar.suggest.bookmark", true); - do_log_info("URL: Restrict bookmarks, common visit, should not autoFill"); + do_print("URL: Restrict bookmarks, common visit, should not autoFill"); yield check_autocomplete({ search: "visited/v", matches: [ ], @@ -256,7 +256,7 @@ add_task(function* test_default_behavior_url() { completed: "visited/v" }); - do_log_info("URL: Restrict bookmarks, typed visit, should not autoFill"); + do_print("URL: Restrict bookmarks, typed visit, should not autoFill"); yield check_autocomplete({ search: "typed/t", matches: [ ], @@ -265,7 +265,7 @@ add_task(function* test_default_behavior_url() { }); // Don't autoFill this one cause it's not typed. - do_log_info("URL: Restrict bookmarks, bookmark, should not autoFill"); + do_print("URL: Restrict bookmarks, bookmark, should not autoFill"); yield check_autocomplete({ search: "bookmarked/b", matches: [ { uri: uri3, title: "bookmarked", style: [ "bookmark" ] } ], @@ -274,7 +274,7 @@ add_task(function* test_default_behavior_url() { }); // Note we don't show this one cause it's not typed. - do_log_info("URL: Restrict bookmarks, typed bookmark, should autoFill"); + do_print("URL: Restrict bookmarks, typed bookmark, should autoFill"); yield check_autocomplete({ search: "tpbk/t", matches: [ { uri: uri4, title: "tpbk/tp/", style: [ "autofill" ] } ], @@ -284,7 +284,7 @@ add_task(function* test_default_behavior_url() { Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", false); - do_log_info("URL: Restrict bookmarks, bookmark, autofill.typed = false, should autoFill"); + do_print("URL: Restrict bookmarks, bookmark, autofill.typed = false, should autoFill"); yield check_autocomplete({ search: "bookmarked/b", matches: [ { uri: uri3, title: "bookmarked/bo/", style: [ "autofill" ] } ], diff --git a/toolkit/components/places/tests/unifiedcomplete/test_autocomplete_functional.js b/toolkit/components/places/tests/unifiedcomplete/test_autocomplete_functional.js index bae14734ddf..f77bb7555a2 100644 --- a/toolkit/components/places/tests/unifiedcomplete/test_autocomplete_functional.js +++ b/toolkit/components/places/tests/unifiedcomplete/test_autocomplete_functional.js @@ -7,7 +7,7 @@ const PREF_AUTOCOMPLETE_ENABLED = "browser.urlbar.autocomplete.enabled"; add_task(function* test_disabling_autocomplete() { - do_log_info("Check disabling autocomplete disables autofill"); + do_print("Check disabling autocomplete disables autofill"); Services.prefs.setBoolPref(PREF_AUTOCOMPLETE_ENABLED, false); yield promiseAddVisits({ uri: NetUtil.newURI("http://visit.mozilla.org"), transition: TRANSITION_TYPED }); @@ -20,7 +20,7 @@ add_task(function* test_disabling_autocomplete() { }); add_task(function* test_urls_order() { - do_log_info("Add urls, check for correct order"); + do_print("Add urls, check for correct order"); let places = [{ uri: NetUtil.newURI("http://visit1.mozilla.org") }, { uri: NetUtil.newURI("http://visit2.mozilla.org"), transition: TRANSITION_TYPED }]; @@ -34,7 +34,7 @@ add_task(function* test_urls_order() { }); add_task(function* test_ignore_prefix() { - do_log_info("Add urls, make sure www and http are ignored"); + do_print("Add urls, make sure www and http are ignored"); Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", false); yield promiseAddVisits(NetUtil.newURI("http://www.visit1.mozilla.org")); yield check_autocomplete({ @@ -46,7 +46,7 @@ add_task(function* test_ignore_prefix() { }); add_task(function* test_after_host() { - do_log_info("Autocompleting after an existing host completes to the url"); + do_print("Autocompleting after an existing host completes to the url"); Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", false); yield promiseAddVisits(NetUtil.newURI("http://www.visit3.mozilla.org")); yield check_autocomplete({ @@ -58,7 +58,7 @@ add_task(function* test_after_host() { }); add_task(function* test_respect_www() { - do_log_info("Searching for www.me should yield www.me.mozilla.org/"); + do_print("Searching for www.me should yield www.me.mozilla.org/"); Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", false); yield promiseAddVisits(NetUtil.newURI("http://www.me.mozilla.org")); yield check_autocomplete({ @@ -70,7 +70,7 @@ add_task(function* test_respect_www() { }); add_task(function* test_bookmark_first() { - do_log_info("With a bookmark and history, the query result should be the bookmark"); + do_print("With a bookmark and history, the query result should be the bookmark"); Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", false); addBookmark({ uri: NetUtil.newURI("http://bookmark1.mozilla.org/") }); yield promiseAddVisits(NetUtil.newURI("http://bookmark1.mozilla.org/foo")); @@ -83,7 +83,7 @@ add_task(function* test_bookmark_first() { }); add_task(function* test_full_path() { - do_log_info("Check to make sure we get the proper results with full paths"); + do_print("Check to make sure we get the proper results with full paths"); Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", false); let places = [{ uri: NetUtil.newURI("http://smokey.mozilla.org/foo/bar/baz?bacon=delicious") }, { uri: NetUtil.newURI("http://smokey.mozilla.org/foo/bar/baz?bacon=smokey") }]; @@ -97,7 +97,7 @@ add_task(function* test_full_path() { }); add_task(function* test_complete_to_slash() { - do_log_info("Check to make sure we autocomplete to the following '/'"); + do_print("Check to make sure we autocomplete to the following '/'"); Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", false); let places = [{ uri: NetUtil.newURI("http://smokey.mozilla.org/foo/bar/baz?bacon=delicious") }, { uri: NetUtil.newURI("http://smokey.mozilla.org/foo/bar/baz?bacon=smokey") }]; @@ -111,7 +111,7 @@ add_task(function* test_complete_to_slash() { }); add_task(function* test_complete_to_slash_with_www() { - do_log_info("Check to make sure we autocomplete to the following '/'"); + do_print("Check to make sure we autocomplete to the following '/'"); Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", false); let places = [{ uri: NetUtil.newURI("http://www.smokey.mozilla.org/foo/bar/baz?bacon=delicious") }, { uri: NetUtil.newURI("http://www.smokey.mozilla.org/foo/bar/baz?bacon=smokey") }]; @@ -125,7 +125,7 @@ add_task(function* test_complete_to_slash_with_www() { }); add_task(function* test_complete_querystring() { - do_log_info("Check to make sure we autocomplete after ?"); + do_print("Check to make sure we autocomplete after ?"); Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", false); yield promiseAddVisits(NetUtil.newURI("http://smokey.mozilla.org/foo?bacon=delicious")); yield check_autocomplete({ @@ -137,7 +137,7 @@ add_task(function* test_complete_querystring() { }); add_task(function* test_complete_fragment() { - do_log_info("Check to make sure we autocomplete after #"); + do_print("Check to make sure we autocomplete after #"); Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", false); yield promiseAddVisits(NetUtil.newURI("http://smokey.mozilla.org/foo?bacon=delicious#bar")); yield check_autocomplete({ diff --git a/toolkit/components/places/tests/unifiedcomplete/test_avoid_middle_complete.js b/toolkit/components/places/tests/unifiedcomplete/test_avoid_middle_complete.js index 1fb62e9c6f0..ed552c42414 100644 --- a/toolkit/components/places/tests/unifiedcomplete/test_avoid_middle_complete.js +++ b/toolkit/components/places/tests/unifiedcomplete/test_avoid_middle_complete.js @@ -6,7 +6,7 @@ add_task(function* test_prefix_space_noautofill() { yield promiseAddVisits({ uri: NetUtil.newURI("http://moz.org/test/"), transition: TRANSITION_TYPED }); - do_log_info("Should not try to autoFill if search string contains a space"); + do_print("Should not try to autoFill if search string contains a space"); yield check_autocomplete({ search: " mo", autofilled: " mo", @@ -20,7 +20,7 @@ add_task(function* test_trailing_space_noautofill() { yield promiseAddVisits({ uri: NetUtil.newURI("http://moz.org/test/"), transition: TRANSITION_TYPED }); - do_log_info("Should not try to autoFill if search string contains a space"); + do_print("Should not try to autoFill if search string contains a space"); yield check_autocomplete({ search: "mo ", autofilled: "mo ", @@ -38,7 +38,7 @@ add_task(function* test_searchEngine_autofill() { engine.addParam("q", "{searchTerms}", null); do_register_cleanup(() => Services.search.removeEngine(engine)); - do_log_info("Should autoFill search engine if search string does not contains a space"); + do_print("Should autoFill search engine if search string does not contains a space"); yield check_autocomplete({ search: "ca", autofilled: "cake.search", @@ -56,7 +56,7 @@ add_task(function* test_searchEngine_prefix_space_noautofill() { engine.addParam("q", "{searchTerms}", null); do_register_cleanup(() => Services.search.removeEngine(engine)); - do_log_info("Should not try to autoFill search engine if search string contains a space"); + do_print("Should not try to autoFill search engine if search string contains a space"); yield check_autocomplete({ search: " cu", autofilled: " cu", @@ -74,7 +74,7 @@ add_task(function* test_searchEngine_trailing_space_noautofill() { engine.addParam("q", "{searchTerms}", null); do_register_cleanup(() => Services.search.removeEngine(engine)); - do_log_info("Should not try to autoFill search engine if search string contains a space"); + do_print("Should not try to autoFill search engine if search string contains a space"); yield check_autocomplete({ search: "ba ", autofilled: "ba ", @@ -92,7 +92,7 @@ add_task(function* test_searchEngine_www_noautofill() { engine.addParam("q", "{searchTerms}", null); do_register_cleanup(() => Services.search.removeEngine(engine)); - do_log_info("Should not autoFill search engine if search string contains www. but engine doesn't"); + do_print("Should not autoFill search engine if search string contains www. but engine doesn't"); yield check_autocomplete({ search: "www.ham", autofilled: "www.ham", @@ -110,7 +110,7 @@ add_task(function* test_searchEngine_different_scheme_noautofill() { engine.addParam("q", "{searchTerms}", null); do_register_cleanup(() => Services.search.removeEngine(engine)); - do_log_info("Should not autoFill search engine if search string has a different scheme."); + do_print("Should not autoFill search engine if search string has a different scheme."); yield check_autocomplete({ search: "http://pie", autofilled: "http://pie", @@ -129,21 +129,21 @@ add_task(function* test_searchEngine_matching_prefix_autofill() { do_register_cleanup(() => Services.search.removeEngine(engine)); - do_log_info("Should autoFill search engine if search string has matching prefix."); + do_print("Should autoFill search engine if search string has matching prefix."); yield check_autocomplete({ search: "http://www.be", autofilled: "http://www.bean.search", completed: "http://www.bean.search" }) - do_log_info("Should autoFill search engine if search string has www prefix."); + do_print("Should autoFill search engine if search string has www prefix."); yield check_autocomplete({ search: "www.be", autofilled: "www.bean.search", completed: "http://www.bean.search" }); - do_log_info("Should autoFill search engine if search string has matching scheme."); + do_print("Should autoFill search engine if search string has matching scheme."); yield check_autocomplete({ search: "http://be", autofilled: "http://bean.search", @@ -159,7 +159,7 @@ add_task(function* test_prefix_autofill() { yield promiseAddVisits({ uri: NetUtil.newURI("http://moz.org/test/"), transition: TRANSITION_TYPED }); - do_log_info("Should not try to autoFill in-the-middle if a search is canceled immediately"); + do_print("Should not try to autoFill in-the-middle if a search is canceled immediately"); yield check_autocomplete({ incompleteSearch: "moz", search: "mozi", diff --git a/toolkit/components/places/tests/unifiedcomplete/test_avoid_stripping_to_empty_tokens.js b/toolkit/components/places/tests/unifiedcomplete/test_avoid_stripping_to_empty_tokens.js index ec62a160cc5..917bfad6018 100644 --- a/toolkit/components/places/tests/unifiedcomplete/test_avoid_stripping_to_empty_tokens.js +++ b/toolkit/components/places/tests/unifiedcomplete/test_avoid_stripping_to_empty_tokens.js @@ -28,7 +28,7 @@ add_task(function* test_protocol_trimming() { "www.mo te" ]; for (let input of inputs) { - do_log_info("Searching for: " + input); + do_print("Searching for: " + input); yield check_autocomplete({ search: input, matches: matches diff --git a/toolkit/components/places/tests/unifiedcomplete/test_casing.js b/toolkit/components/places/tests/unifiedcomplete/test_casing.js index 8a2156d3e7b..e31548d6b1d 100644 --- a/toolkit/components/places/tests/unifiedcomplete/test_casing.js +++ b/toolkit/components/places/tests/unifiedcomplete/test_casing.js @@ -3,7 +3,7 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ add_task(function* test_casing_1() { - do_log_info("Searching for cased entry 1"); + do_print("Searching for cased entry 1"); yield promiseAddVisits({ uri: NetUtil.newURI("http://mozilla.org/test/"), transition: TRANSITION_TYPED }); yield check_autocomplete({ @@ -15,7 +15,7 @@ add_task(function* test_casing_1() { }); add_task(function* test_casing_2() { - do_log_info("Searching for cased entry 2"); + do_print("Searching for cased entry 2"); yield promiseAddVisits({ uri: NetUtil.newURI("http://mozilla.org/test/"), transition: TRANSITION_TYPED }); yield check_autocomplete({ @@ -27,7 +27,7 @@ add_task(function* test_casing_2() { }); add_task(function* test_casing_3() { - do_log_info("Searching for cased entry 3"); + do_print("Searching for cased entry 3"); yield promiseAddVisits({ uri: NetUtil.newURI("http://mozilla.org/Test/"), transition: TRANSITION_TYPED }); yield check_autocomplete({ @@ -39,7 +39,7 @@ add_task(function* test_casing_3() { }); add_task(function* test_casing_4() { - do_log_info("Searching for cased entry 4"); + do_print("Searching for cased entry 4"); yield promiseAddVisits({ uri: NetUtil.newURI("http://mozilla.org/Test/"), transition: TRANSITION_TYPED }); yield check_autocomplete({ @@ -51,7 +51,7 @@ add_task(function* test_casing_4() { }); add_task(function* test_casing_5() { - do_log_info("Searching for cased entry 5"); + do_print("Searching for cased entry 5"); yield promiseAddVisits({ uri: NetUtil.newURI("http://mozilla.org/Test/"), transition: TRANSITION_TYPED }); yield check_autocomplete({ @@ -63,7 +63,7 @@ add_task(function* test_casing_5() { }); add_task(function* test_untrimmed_casing() { - do_log_info("Searching for untrimmed cased entry"); + do_print("Searching for untrimmed cased entry"); yield promiseAddVisits({ uri: NetUtil.newURI("http://mozilla.org/Test/"), transition: TRANSITION_TYPED }); yield check_autocomplete({ @@ -75,7 +75,7 @@ add_task(function* test_untrimmed_casing() { }); add_task(function* test_untrimmed_www_casing() { - do_log_info("Searching for untrimmed cased entry with www"); + do_print("Searching for untrimmed cased entry with www"); yield promiseAddVisits({ uri: NetUtil.newURI("http://www.mozilla.org/Test/"), transition: TRANSITION_TYPED }); yield check_autocomplete({ @@ -87,7 +87,7 @@ add_task(function* test_untrimmed_www_casing() { }); add_task(function* test_untrimmed_path_casing() { - do_log_info("Searching for untrimmed cased entry with path"); + do_print("Searching for untrimmed cased entry with path"); yield promiseAddVisits({ uri: NetUtil.newURI("http://mozilla.org/Test/"), transition: TRANSITION_TYPED }); yield check_autocomplete({ @@ -99,7 +99,7 @@ add_task(function* test_untrimmed_path_casing() { }); add_task(function* test_untrimmed_path_casing_2() { - do_log_info("Searching for untrimmed cased entry with path 2"); + do_print("Searching for untrimmed cased entry with path 2"); yield promiseAddVisits({ uri: NetUtil.newURI("http://mozilla.org/Test/"), transition: TRANSITION_TYPED }); yield check_autocomplete({ @@ -111,7 +111,7 @@ add_task(function* test_untrimmed_path_casing_2() { }); add_task(function* test_untrimmed_path_www_casing() { - do_log_info("Searching for untrimmed cased entry with www and path"); + do_print("Searching for untrimmed cased entry with www and path"); yield promiseAddVisits({ uri: NetUtil.newURI("http://www.mozilla.org/Test/"), transition: TRANSITION_TYPED }); yield check_autocomplete({ @@ -123,7 +123,7 @@ add_task(function* test_untrimmed_path_www_casing() { }); add_task(function* test_untrimmed_path_www_casing_2() { - do_log_info("Searching for untrimmed cased entry with www and path 2"); + do_print("Searching for untrimmed cased entry with www and path 2"); yield promiseAddVisits({ uri: NetUtil.newURI("http://www.mozilla.org/Test/"), transition: TRANSITION_TYPED }); yield check_autocomplete({ diff --git a/toolkit/components/places/tests/unifiedcomplete/test_do_not_trim.js b/toolkit/components/places/tests/unifiedcomplete/test_do_not_trim.js index 7c1862ef151..e92acef3c65 100644 --- a/toolkit/components/places/tests/unifiedcomplete/test_do_not_trim.js +++ b/toolkit/components/places/tests/unifiedcomplete/test_do_not_trim.js @@ -6,7 +6,7 @@ // that largely confuses completeDefaultIndex add_task(function* test_not_autofill_ws_1() { - do_log_info("Do not autofill whitespaced entry 1"); + do_print("Do not autofill whitespaced entry 1"); yield promiseAddVisits({ uri: NetUtil.newURI("http://mozilla.org/link/"), transition: TRANSITION_TYPED }); yield check_autocomplete({ @@ -18,7 +18,7 @@ add_task(function* test_not_autofill_ws_1() { }); add_task(function* test_not_autofill_ws_2() { - do_log_info("Do not autofill whitespaced entry 2"); + do_print("Do not autofill whitespaced entry 2"); yield promiseAddVisits({ uri: NetUtil.newURI("http://mozilla.org/link/"), transition: TRANSITION_TYPED }); yield check_autocomplete({ @@ -30,7 +30,7 @@ add_task(function* test_not_autofill_ws_2() { }); add_task(function* test_not_autofill_ws_3() { - do_log_info("Do not autofill whitespaced entry 3"); + do_print("Do not autofill whitespaced entry 3"); yield promiseAddVisits({ uri: NetUtil.newURI("http://mozilla.org/link/"), transition: TRANSITION_TYPED }); yield check_autocomplete({ @@ -42,7 +42,7 @@ add_task(function* test_not_autofill_ws_3() { }); add_task(function* test_not_autofill_ws_4() { - do_log_info("Do not autofill whitespaced entry 4"); + do_print("Do not autofill whitespaced entry 4"); yield promiseAddVisits({ uri: NetUtil.newURI("http://mozilla.org/link/"), transition: TRANSITION_TYPED }); yield check_autocomplete({ @@ -55,7 +55,7 @@ add_task(function* test_not_autofill_ws_4() { add_task(function* test_not_autofill_ws_5() { - do_log_info("Do not autofill whitespaced entry 5"); + do_print("Do not autofill whitespaced entry 5"); yield promiseAddVisits({ uri: NetUtil.newURI("http://mozilla.org/link/"), transition: TRANSITION_TYPED }); yield check_autocomplete({ @@ -67,7 +67,7 @@ add_task(function* test_not_autofill_ws_5() { }); add_task(function* test_not_autofill_ws_6() { - do_log_info("Do not autofill whitespaced entry 6"); + do_print("Do not autofill whitespaced entry 6"); yield promiseAddVisits({ uri: NetUtil.newURI("http://mozilla.org/link/"), transition: TRANSITION_TYPED }); yield check_autocomplete({ diff --git a/toolkit/components/places/tests/unifiedcomplete/test_download_embed_bookmarks.js b/toolkit/components/places/tests/unifiedcomplete/test_download_embed_bookmarks.js index 4b30406a6f0..e2033436c69 100644 --- a/toolkit/components/places/tests/unifiedcomplete/test_download_embed_bookmarks.js +++ b/toolkit/components/places/tests/unifiedcomplete/test_download_embed_bookmarks.js @@ -35,37 +35,37 @@ add_task(function* test_download_embed_bookmarks() { addBookmark({ uri: uri3, title: "framed-bookmark" }); - do_log_info("Searching for bookmarked download uri matches"); + do_print("Searching for bookmarked download uri matches"); yield check_autocomplete({ search: "download-bookmark", matches: [ { uri: uri1, title: "download-bookmark", style: [ "bookmark" ] } ] }); - do_log_info("Searching for bookmarked embed uri matches"); + do_print("Searching for bookmarked embed uri matches"); yield check_autocomplete({ search: "embed-bookmark", matches: [ { uri: uri2, title: "embed-bookmark", style: [ "bookmark" ] } ] }); - do_log_info("Searching for bookmarked framed uri matches"); + do_print("Searching for bookmarked framed uri matches"); yield check_autocomplete({ search: "framed-bookmark", matches: [ { uri: uri3, title: "framed-bookmark", style: [ "bookmark" ] } ] }); - do_log_info("Searching for download uri does not match"); + do_print("Searching for download uri does not match"); yield check_autocomplete({ search: "download2", matches: [ ] }); - do_log_info("Searching for embed uri does not match"); + do_print("Searching for embed uri does not match"); yield check_autocomplete({ search: "embed2", matches: [ ] }); - do_log_info("Searching for framed uri does not match"); + do_print("Searching for framed uri does not match"); yield check_autocomplete({ search: "framed2", matches: [ ] diff --git a/toolkit/components/places/tests/unifiedcomplete/test_dupe_urls.js b/toolkit/components/places/tests/unifiedcomplete/test_dupe_urls.js index 7415dca8ae6..9ba6ef9a94a 100644 --- a/toolkit/components/places/tests/unifiedcomplete/test_dupe_urls.js +++ b/toolkit/components/places/tests/unifiedcomplete/test_dupe_urls.js @@ -4,7 +4,7 @@ // Ensure inline autocomplete doesn't return zero frecency pages. add_task(function* test_dupe_urls() { - do_log_info("Searching for urls with dupes should only show one"); + do_print("Searching for urls with dupes should only show one"); yield promiseAddVisits({ uri: NetUtil.newURI("http://mozilla.org/"), transition: TRANSITION_TYPED }, { uri: NetUtil.newURI("http://mozilla.org/?") }); diff --git a/toolkit/components/places/tests/unifiedcomplete/test_empty_search.js b/toolkit/components/places/tests/unifiedcomplete/test_empty_search.js index ed52c6d60b7..d8ac6a89647 100644 --- a/toolkit/components/places/tests/unifiedcomplete/test_empty_search.js +++ b/toolkit/components/places/tests/unifiedcomplete/test_empty_search.js @@ -40,7 +40,7 @@ add_task(function* test_javascript_match() { // Now remove page 6 from history, so it is an unvisited bookmark. PlacesUtils.history.removePage(uri6); - do_log_info("Match everything"); + do_print("Match everything"); yield check_autocomplete({ search: "foo", searchParam: "enable-actions", @@ -53,21 +53,21 @@ add_task(function* test_javascript_match() { { uri: makeActionURI("switchtab", {url: "http://t.foo/6"}), title: "title", style: [ "action,switchtab" ] }, ] }); - do_log_info("Match only typed history"); + do_print("Match only typed history"); yield check_autocomplete({ search: "foo ^ ~", matches: [ { uri: uri3, title: "title" }, { uri: uri4, title: "title" } ] }); - do_log_info("Drop-down empty search matches only typed history"); + do_print("Drop-down empty search matches only typed history"); yield check_autocomplete({ search: "", matches: [ { uri: uri3, title: "title" }, { uri: uri4, title: "title" } ] }); - do_log_info("Drop-down empty search matches only bookmarks"); + do_print("Drop-down empty search matches only bookmarks"); Services.prefs.setBoolPref("browser.urlbar.suggest.history", false); Services.prefs.setBoolPref("browser.urlbar.suggest.bookmark", true); yield check_autocomplete({ @@ -78,7 +78,7 @@ add_task(function* test_javascript_match() { { uri: uri6, title: "title", style: ["bookmark"] } ] }); - do_log_info("Drop-down empty search matches only open tabs"); + do_print("Drop-down empty search matches only open tabs"); Services.prefs.setBoolPref("browser.urlbar.suggest.bookmark", false); yield check_autocomplete({ search: "", diff --git a/toolkit/components/places/tests/unifiedcomplete/test_enabled.js b/toolkit/components/places/tests/unifiedcomplete/test_enabled.js index 4e83f99ac86..69b15c2e0ac 100644 --- a/toolkit/components/places/tests/unifiedcomplete/test_enabled.js +++ b/toolkit/components/places/tests/unifiedcomplete/test_enabled.js @@ -11,20 +11,20 @@ add_task(function* test_enabled() { let uri = NetUtil.newURI("http://url/0"); yield promiseAddVisits([ { uri: uri, title: "title" } ]); - do_log_info("plain search"); + do_print("plain search"); yield check_autocomplete({ search: "url", matches: [ { uri: uri, title: "title" } ] }); - do_log_info("search disabled"); + do_print("search disabled"); Services.prefs.setBoolPref("browser.urlbar.autocomplete.enabled", false); yield check_autocomplete({ search: "url", matches: [ ] }); - do_log_info("resume normal search"); + do_print("resume normal search"); Services.prefs.setBoolPref("browser.urlbar.autocomplete.enabled", true); yield check_autocomplete({ search: "url", diff --git a/toolkit/components/places/tests/unifiedcomplete/test_escape_self.js b/toolkit/components/places/tests/unifiedcomplete/test_escape_self.js index a4830ecbdff..1ef51a28663 100644 --- a/toolkit/components/places/tests/unifiedcomplete/test_escape_self.js +++ b/toolkit/components/places/tests/unifiedcomplete/test_escape_self.js @@ -13,13 +13,13 @@ add_task(function* test_escape() { yield promiseAddVisits([ { uri: uri1, title: "title" }, { uri: uri2, title: "title" } ]); - do_log_info("Unescaped location matches itself"); + do_print("Unescaped location matches itself"); yield check_autocomplete({ search: "http://unescapeduri/", matches: [ { uri: uri1, title: "title" } ] }); - do_log_info("Escaped location matches itself"); + do_print("Escaped location matches itself"); yield check_autocomplete({ search: "http://escapeduri/%40/", matches: [ { uri: uri2, title: "title" } ] diff --git a/toolkit/components/places/tests/unifiedcomplete/test_ignore_protocol.js b/toolkit/components/places/tests/unifiedcomplete/test_ignore_protocol.js index 53070e005a1..8b670f5d2fc 100644 --- a/toolkit/components/places/tests/unifiedcomplete/test_ignore_protocol.js +++ b/toolkit/components/places/tests/unifiedcomplete/test_ignore_protocol.js @@ -12,7 +12,7 @@ add_task(function* test_escape() { yield promiseAddVisits([ { uri: uri1, title: "title" }, { uri: uri2, title: "title" } ]); - do_log_info("Searching for h matches site and not http://"); + do_print("Searching for h matches site and not http://"); yield check_autocomplete({ search: "h", matches: [ { uri: uri2, title: "title" } ] diff --git a/toolkit/components/places/tests/unifiedcomplete/test_keyword_search.js b/toolkit/components/places/tests/unifiedcomplete/test_keyword_search.js index d9b3923b885..4d40b69adbf 100644 --- a/toolkit/components/places/tests/unifiedcomplete/test_keyword_search.js +++ b/toolkit/components/places/tests/unifiedcomplete/test_keyword_search.js @@ -19,43 +19,43 @@ add_task(function* test_keyword_searc() { { uri: uri2, title: "Generic page title" } ]); addBookmark({ uri: uri1, title: "Keyword title", keyword: "key"}); - do_log_info("Plain keyword query"); + do_print("Plain keyword query"); yield check_autocomplete({ search: "key term", matches: [ { uri: NetUtil.newURI("http://abc/?search=term"), title: "Keyword title", style: ["keyword"] } ] }); - do_log_info("Multi-word keyword query"); + do_print("Multi-word keyword query"); yield check_autocomplete({ search: "key multi word", matches: [ { uri: NetUtil.newURI("http://abc/?search=multi+word"), title: "Keyword title", style: ["keyword"] } ] }); - do_log_info("Keyword query with +"); + do_print("Keyword query with +"); yield check_autocomplete({ search: "key blocking+", matches: [ { uri: NetUtil.newURI("http://abc/?search=blocking%2B"), title: "Keyword title", style: ["keyword"] } ] }); - do_log_info("Unescaped term in query"); + do_print("Unescaped term in query"); yield check_autocomplete({ search: "key ユニコード", matches: [ { uri: NetUtil.newURI("http://abc/?search=ユニコード"), title: "Keyword title", style: ["keyword"] } ] }); - do_log_info("Keyword that happens to match a page"); + do_print("Keyword that happens to match a page"); yield check_autocomplete({ search: "key ThisPageIsInHistory", matches: [ { uri: NetUtil.newURI("http://abc/?search=ThisPageIsInHistory"), title: "Generic page title", style: ["bookmark"] } ] }); - do_log_info("Keyword without query (without space)"); + do_print("Keyword without query (without space)"); yield check_autocomplete({ search: "key", matches: [ { uri: NetUtil.newURI("http://abc/?search="), title: "Keyword title", style: ["keyword"] } ] }); - do_log_info("Keyword without query (with space)"); + do_print("Keyword without query (with space)"); yield check_autocomplete({ search: "key ", matches: [ { uri: NetUtil.newURI("http://abc/?search="), title: "Keyword title", style: ["keyword"] } ] @@ -66,7 +66,7 @@ add_task(function* test_keyword_searc() { yield promiseAddVisits([ { uri: uri3, title: "Generic page title" } ]); addBookmark({ uri: uri3, title: "Keyword title", keyword: "key", style: ["keyword"] }); - do_log_info("Two keywords matched"); + do_print("Two keywords matched"); yield check_autocomplete({ search: "key twoKey", matches: [ { uri: NetUtil.newURI("http://abc/?search=twoKey"), title: "Keyword title", style: ["keyword"] }, diff --git a/toolkit/components/places/tests/unifiedcomplete/test_keyword_search_actions.js b/toolkit/components/places/tests/unifiedcomplete/test_keyword_search_actions.js index fa80fbae767..aba1631ae27 100644 --- a/toolkit/components/places/tests/unifiedcomplete/test_keyword_search_actions.js +++ b/toolkit/components/places/tests/unifiedcomplete/test_keyword_search_actions.js @@ -19,49 +19,49 @@ add_task(function* test_keyword_search() { { uri: uri2, title: "Generic page title" } ]); addBookmark({ uri: uri1, title: "Keyword title", keyword: "key"}); - do_log_info("Plain keyword query"); + do_print("Plain keyword query"); yield check_autocomplete({ search: "key term", searchParam: "enable-actions", matches: [ { uri: makeActionURI("keyword", {url: "http://abc/?search=term", input: "key term"}), title: "Keyword title", style: [ "action", "keyword" ] } ] }); - do_log_info("Multi-word keyword query"); + do_print("Multi-word keyword query"); yield check_autocomplete({ search: "key multi word", searchParam: "enable-actions", matches: [ { uri: makeActionURI("keyword", {url: "http://abc/?search=multi+word", input: "key multi word"}), title: "Keyword title", style: [ "action", "keyword" ] } ] }); - do_log_info("Keyword query with +"); + do_print("Keyword query with +"); yield check_autocomplete({ search: "key blocking+", searchParam: "enable-actions", matches: [ { uri: makeActionURI("keyword", {url: "http://abc/?search=blocking%2B", input: "key blocking+"}), title: "Keyword title", style: [ "action", "keyword" ] } ] }); - do_log_info("Unescaped term in query"); + do_print("Unescaped term in query"); yield check_autocomplete({ search: "key ユニコード", searchParam: "enable-actions", matches: [ { uri: makeActionURI("keyword", {url: "http://abc/?search=ユニコード", input: "key ユニコード"}), title: "Keyword title", style: [ "action", "keyword" ] } ] }); - do_log_info("Keyword that happens to match a page"); + do_print("Keyword that happens to match a page"); yield check_autocomplete({ search: "key ThisPageIsInHistory", searchParam: "enable-actions", matches: [ { uri: makeActionURI("keyword", {url: "http://abc/?search=ThisPageIsInHistory", input: "key ThisPageIsInHistory"}), title: "Keyword title", style: [ "action", "keyword" ] } ] }); - do_log_info("Keyword without query (without space)"); + do_print("Keyword without query (without space)"); yield check_autocomplete({ search: "key", searchParam: "enable-actions", matches: [ { uri: makeActionURI("keyword", {url: "http://abc/?search=", input: "key"}), title: "Keyword title", style: [ "action", "keyword" ] } ] }); - do_log_info("Keyword without query (with space)"); + do_print("Keyword without query (with space)"); yield check_autocomplete({ search: "key ", searchParam: "enable-actions", @@ -73,7 +73,7 @@ add_task(function* test_keyword_search() { yield promiseAddVisits([ { uri: uri3, title: "Generic page title" } ]); addBookmark({ uri: uri3, title: "Keyword title", keyword: "key"}); - do_log_info("Two keywords matched"); + do_print("Two keywords matched"); yield check_autocomplete({ search: "key twoKey", searchParam: "enable-actions", diff --git a/toolkit/components/places/tests/unifiedcomplete/test_keywords.js b/toolkit/components/places/tests/unifiedcomplete/test_keywords.js index d14ec2bc690..248a9b51d48 100644 --- a/toolkit/components/places/tests/unifiedcomplete/test_keywords.js +++ b/toolkit/components/places/tests/unifiedcomplete/test_keywords.js @@ -3,7 +3,7 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ add_task(function* test_non_keyword() { - do_log_info("Searching for non-keyworded entry should autoFill it"); + do_print("Searching for non-keyworded entry should autoFill it"); yield promiseAddVisits({ uri: NetUtil.newURI("http://mozilla.org/test/"), transition: TRANSITION_TYPED }); addBookmark({ uri: NetUtil.newURI("http://mozilla.org/test/") }); @@ -16,7 +16,7 @@ add_task(function* test_non_keyword() { }); add_task(function* test_keyword() { - do_log_info("Searching for keyworded entry should not autoFill it"); + do_print("Searching for keyworded entry should not autoFill it"); yield promiseAddVisits({ uri: NetUtil.newURI("http://mozilla.org/test/"), transition: TRANSITION_TYPED }); addBookmark({ uri: NetUtil.newURI("http://mozilla.org/test/"), keyword: "moz" }); @@ -29,7 +29,7 @@ add_task(function* test_keyword() { }); add_task(function* test_more_than_keyword() { - do_log_info("Searching for more than keyworded entry should autoFill it"); + do_print("Searching for more than keyworded entry should autoFill it"); yield promiseAddVisits({ uri: NetUtil.newURI("http://mozilla.org/test/"), transition: TRANSITION_TYPED }); addBookmark({ uri: NetUtil.newURI("http://mozilla.org/test/"), keyword: "moz" }); @@ -42,7 +42,7 @@ add_task(function* test_more_than_keyword() { }); add_task(function* test_less_than_keyword() { - do_log_info("Searching for less than keyworded entry should autoFill it"); + do_print("Searching for less than keyworded entry should autoFill it"); yield promiseAddVisits({ uri: NetUtil.newURI("http://mozilla.org/test/"), transition: TRANSITION_TYPED }); addBookmark({ uri: NetUtil.newURI("http://mozilla.org/test/"), keyword: "moz" }); @@ -55,7 +55,7 @@ add_task(function* test_less_than_keyword() { }); add_task(function* test_keyword_casing() { - do_log_info("Searching for keyworded entry is case-insensitive"); + do_print("Searching for keyworded entry is case-insensitive"); yield promiseAddVisits({ uri: NetUtil.newURI("http://mozilla.org/test/"), transition: TRANSITION_TYPED }); addBookmark({ uri: NetUtil.newURI("http://mozilla.org/test/"), keyword: "moz" }); diff --git a/toolkit/components/places/tests/unifiedcomplete/test_match_beginning.js b/toolkit/components/places/tests/unifiedcomplete/test_match_beginning.js index e08c195a37f..84b2c5bf575 100644 --- a/toolkit/components/places/tests/unifiedcomplete/test_match_beginning.js +++ b/toolkit/components/places/tests/unifiedcomplete/test_match_beginning.js @@ -15,32 +15,32 @@ add_task(function* test_match_beginning() { yield promiseAddVisits([ { uri: uri1, title: "a b" }, { uri: uri2, title: "b a" } ]); - do_log_info("Match at the beginning of titles"); + do_print("Match at the beginning of titles"); Services.prefs.setIntPref("browser.urlbar.matchBehavior", 3); yield check_autocomplete({ search: "a", matches: [ { uri: uri1, title: "a b" } ] }); - do_log_info("Match at the beginning of titles"); + do_print("Match at the beginning of titles"); yield check_autocomplete({ search: "b", matches: [ { uri: uri2, title: "b a" } ] }); - do_log_info("Match at the beginning of urls"); + do_print("Match at the beginning of urls"); yield check_autocomplete({ search: "x", matches: [ { uri: uri1, title: "a b" } ] }); - do_log_info("Match at the beginning of urls"); + do_print("Match at the beginning of urls"); yield check_autocomplete({ search: "y", matches: [ { uri: uri2, title: "b a" } ] }); - do_log_info("Sanity check that matching anywhere finds more"); + do_print("Sanity check that matching anywhere finds more"); Services.prefs.setIntPref("browser.urlbar.matchBehavior", 1); yield check_autocomplete({ search: "a", diff --git a/toolkit/components/places/tests/unifiedcomplete/test_multi_word_search.js b/toolkit/components/places/tests/unifiedcomplete/test_multi_word_search.js index e63ed78c734..e7b86cf6f03 100644 --- a/toolkit/components/places/tests/unifiedcomplete/test_multi_word_search.js +++ b/toolkit/components/places/tests/unifiedcomplete/test_multi_word_search.js @@ -24,39 +24,39 @@ add_task(function* test_match_beginning() { addBookmark({ uri: uri3, title: "f(o)o br" }); addBookmark({ uri: uri4, title: "b(a)r bz" }); - do_log_info("Match 2 terms all in url"); + do_print("Match 2 terms all in url"); yield check_autocomplete({ search: "c d", matches: [ { uri: uri1, title: "f(o)o br" } ] }); - do_log_info("Match 1 term in url and 1 term in title"); + do_print("Match 1 term in url and 1 term in title"); yield check_autocomplete({ search: "b e", matches: [ { uri: uri1, title: "f(o)o br" }, { uri: uri2, title: "b(a)r bz" } ] }); - do_log_info("Match 3 terms all in title; display bookmark title if matched"); + do_print("Match 3 terms all in title; display bookmark title if matched"); yield check_autocomplete({ search: "b a z", matches: [ { uri: uri2, title: "b(a)r bz" }, { uri: uri4, title: "b(a)r bz", style: [ "bookmark" ] } ] }); - do_log_info("Match 2 terms in url and 1 in title; make sure bookmark title is used for search"); + do_print("Match 2 terms in url and 1 in title; make sure bookmark title is used for search"); yield check_autocomplete({ search: "k f t", matches: [ { uri: uri3, title: "f(o)o br", style: [ "bookmark" ] } ] }); - do_log_info("Match 3 terms in url and 1 in title"); + do_print("Match 3 terms in url and 1 in title"); yield check_autocomplete({ search: "d i g z", matches: [ { uri: uri2, title: "b(a)r bz" } ] }); - do_log_info("Match nothing"); + do_print("Match nothing"); yield check_autocomplete({ search: "m o z i", matches: [ ] diff --git a/toolkit/components/places/tests/unifiedcomplete/test_queryurl.js b/toolkit/components/places/tests/unifiedcomplete/test_queryurl.js index 2d25d5ceba2..e74d8b1e55e 100644 --- a/toolkit/components/places/tests/unifiedcomplete/test_queryurl.js +++ b/toolkit/components/places/tests/unifiedcomplete/test_queryurl.js @@ -3,7 +3,7 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ add_task(function* test_no_slash() { - do_log_info("Searching for host match without slash should match host"); + do_print("Searching for host match without slash should match host"); yield promiseAddVisits({ uri: NetUtil.newURI("http://file.org/test/"), transition: TRANSITION_TYPED }, { uri: NetUtil.newURI("file:///c:/test.html"), @@ -17,7 +17,7 @@ add_task(function* test_no_slash() { }); add_task(function* test_w_slash() { - do_log_info("Searching match with slash at the end should do nothing"); + do_print("Searching match with slash at the end should do nothing"); yield promiseAddVisits({ uri: NetUtil.newURI("http://file.org/test/"), transition: TRANSITION_TYPED }, { uri: NetUtil.newURI("file:///c:/test.html"), @@ -31,7 +31,7 @@ add_task(function* test_w_slash() { }); add_task(function* test_middle() { - do_log_info("Searching match with slash in the middle should match url"); + do_print("Searching match with slash in the middle should match url"); yield promiseAddVisits({ uri: NetUtil.newURI("http://file.org/test/"), transition: TRANSITION_TYPED }, { uri: NetUtil.newURI("file:///c:/test.html"), @@ -45,7 +45,7 @@ add_task(function* test_middle() { }); add_task(function* test_nonhost() { - do_log_info("Searching for non-host match without slash should not match url"); + do_print("Searching for non-host match without slash should not match url"); yield promiseAddVisits({ uri: NetUtil.newURI("file:///c:/test.html"), transition: TRANSITION_TYPED }); yield check_autocomplete({ diff --git a/toolkit/components/places/tests/unifiedcomplete/test_searchEngine_current.js b/toolkit/components/places/tests/unifiedcomplete/test_searchEngine_current.js index 8369ffde3d7..0977fe8ebf5 100644 --- a/toolkit/components/places/tests/unifiedcomplete/test_searchEngine_current.js +++ b/toolkit/components/places/tests/unifiedcomplete/test_searchEngine_current.js @@ -10,28 +10,28 @@ add_task(function*() { Services.search.addEngineWithDetails("AliasedMozSearch", "", "doit", "", "GET", "http://s.example.com/search"); - do_log_info("search engine"); + do_print("search engine"); yield check_autocomplete({ search: "mozilla", searchParam: "enable-actions", matches: [ { uri: makeActionURI("searchengine", {engineName: "MozSearch", input: "mozilla", searchQuery: "mozilla"}), title: "MozSearch", style: [ "action", "searchengine" ] }, ] }); - do_log_info("search engine, uri-like input"); + do_print("search engine, uri-like input"); yield check_autocomplete({ search: "http:///", searchParam: "enable-actions", matches: [ { uri: makeActionURI("searchengine", {engineName: "MozSearch", input: "http:///", searchQuery: "http:///"}), title: "MozSearch", style: [ "action", "searchengine" ] }, ] }); - do_log_info("search engine, multiple words"); + do_print("search engine, multiple words"); yield check_autocomplete({ search: "mozzarella cheese", searchParam: "enable-actions", matches: [ { uri: makeActionURI("searchengine", {engineName: "MozSearch", input: "mozzarella cheese", searchQuery: "mozzarella cheese"}), title: "MozSearch", style: [ "action", "searchengine" ] }, ] }); - do_log_info("search engine, after current engine has changed"); + do_print("search engine, after current engine has changed"); Services.search.addEngineWithDetails("MozSearch2", "", "", "", "GET", "http://s.example.com/search2"); engine = Services.search.getEngineByName("MozSearch2"); diff --git a/toolkit/components/places/tests/unifiedcomplete/test_searchEngine_host.js b/toolkit/components/places/tests/unifiedcomplete/test_searchEngine_host.js index ec20ffa4429..af604bdb145 100644 --- a/toolkit/components/places/tests/unifiedcomplete/test_searchEngine_host.js +++ b/toolkit/components/places/tests/unifiedcomplete/test_searchEngine_host.js @@ -55,7 +55,7 @@ add_task(function* test_searchEngine_autoFill() { yield promiseAsyncUpdates(); ok(frecencyForUrl(uri) > 10000, "Added URI should have expected high frecency"); - do_log_info("Check search domain is autoFilled even if there's an higher frecency match"); + do_print("Check search domain is autoFilled even if there's an higher frecency match"); yield check_autocomplete({ search: "my", autofilled: "my.search.com", diff --git a/toolkit/components/places/tests/unifiedcomplete/test_searchEngine_restyle.js b/toolkit/components/places/tests/unifiedcomplete/test_searchEngine_restyle.js index 4c2ddcd80ca..065a1973fb7 100644 --- a/toolkit/components/places/tests/unifiedcomplete/test_searchEngine_restyle.js +++ b/toolkit/components/places/tests/unifiedcomplete/test_searchEngine_restyle.js @@ -14,7 +14,7 @@ add_task(function* test_searchEngine() { yield promiseAddVisits({ uri: uri1, title: "Terms - SearchEngine Search" }); addBookmark({ uri: uri2, title: "Terms - SearchEngine Search" }); - do_log_info("Past search terms should be styled, unless bookmarked"); + do_print("Past search terms should be styled, unless bookmarked"); Services.prefs.setBoolPref("browser.urlbar.restyleSearches", true); yield check_autocomplete({ search: "term", @@ -22,7 +22,7 @@ add_task(function* test_searchEngine() { { uri: uri2, title: "Terms - SearchEngine Search", style: ["bookmark"] } ] }); - do_log_info("Past search terms should not be styled if restyling is disabled"); + do_print("Past search terms should not be styled if restyling is disabled"); Services.prefs.setBoolPref("browser.urlbar.restyleSearches", false); yield check_autocomplete({ search: "term", diff --git a/toolkit/components/places/tests/unifiedcomplete/test_special_search.js b/toolkit/components/places/tests/unifiedcomplete/test_special_search.js index 40287993d51..6a9fa430ca1 100644 --- a/toolkit/components/places/tests/unifiedcomplete/test_special_search.js +++ b/toolkit/components/places/tests/unifiedcomplete/test_special_search.js @@ -45,7 +45,7 @@ add_task(function* test_special_searches() { addBookmark( { uri: uri12, title: "foo.bar", tags: [ "foo.bar" ] } ); // Test restricting searches - do_log_info("History restrict"); + do_print("History restrict"); yield check_autocomplete({ search: "^", matches: [ { uri: uri1, title: "title" }, @@ -56,7 +56,7 @@ add_task(function* test_special_searches() { { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] } ] }); - do_log_info("Star restrict"); + do_print("Star restrict"); yield check_autocomplete({ search: "*", matches: [ { uri: uri5, title: "title", style: [ "bookmark" ] }, @@ -69,7 +69,7 @@ add_task(function* test_special_searches() { { uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] } ] }); - do_log_info("Tag restrict"); + do_print("Tag restrict"); yield check_autocomplete({ search: "+", matches: [ { uri: uri9, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] }, @@ -79,7 +79,7 @@ add_task(function* test_special_searches() { }); // Test specials as any word position - do_log_info("Special as first word"); + do_print("Special as first word"); yield check_autocomplete({ search: "^ foo bar", matches: [ { uri: uri2, title: "foo.bar" }, @@ -89,7 +89,7 @@ add_task(function* test_special_searches() { { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] } ] }); - do_log_info("Special as middle word"); + do_print("Special as middle word"); yield check_autocomplete({ search: "foo ^ bar", matches: [ { uri: uri2, title: "foo.bar" }, @@ -99,7 +99,7 @@ add_task(function* test_special_searches() { { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] } ] }); - do_log_info("Special as last word"); + do_print("Special as last word"); yield check_autocomplete({ search: "foo bar ^", matches: [ { uri: uri2, title: "foo.bar" }, @@ -110,7 +110,7 @@ add_task(function* test_special_searches() { }); // Test restricting and matching searches with a term - do_log_info("foo ^ -> history"); + do_print("foo ^ -> history"); yield check_autocomplete({ search: "foo ^", matches: [ { uri: uri2, title: "foo.bar" }, @@ -120,7 +120,7 @@ add_task(function* test_special_searches() { { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] } ] }); - do_log_info("foo | -> history (change pref)"); + do_print("foo | -> history (change pref)"); changeRestrict("history", "|"); yield check_autocomplete({ search: "foo |", @@ -131,7 +131,7 @@ add_task(function* test_special_searches() { { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] } ] }); - do_log_info("foo * -> is star"); + do_print("foo * -> is star"); resetRestrict("history"); yield check_autocomplete({ search: "foo *", @@ -144,7 +144,7 @@ add_task(function* test_special_searches() { { uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] } ] }); - do_log_info("foo | -> is star (change pref)"); + do_print("foo | -> is star (change pref)"); changeRestrict("bookmark", "|"); yield check_autocomplete({ search: "foo |", @@ -157,7 +157,7 @@ add_task(function* test_special_searches() { { uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] } ] }); - do_log_info("foo # -> in title"); + do_print("foo # -> in title"); resetRestrict("bookmark"); yield check_autocomplete({ search: "foo #", @@ -171,7 +171,7 @@ add_task(function* test_special_searches() { { uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "tag" ] } ] }); - do_log_info("foo | -> in title (change pref)"); + do_print("foo | -> in title (change pref)"); changeRestrict("title", "|"); yield check_autocomplete({ search: "foo |", @@ -185,7 +185,7 @@ add_task(function* test_special_searches() { { uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "tag" ] } ] }); - do_log_info("foo @ -> in url"); + do_print("foo @ -> in url"); resetRestrict("title"); yield check_autocomplete({ search: "foo @", @@ -197,7 +197,7 @@ add_task(function* test_special_searches() { { uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "tag" ] } ] }); - do_log_info("foo | -> in url (change pref)"); + do_print("foo | -> in url (change pref)"); changeRestrict("url", "|"); yield check_autocomplete({ search: "foo |", @@ -209,7 +209,7 @@ add_task(function* test_special_searches() { { uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "tag" ] } ] }); - do_log_info("foo + -> is tag"); + do_print("foo + -> is tag"); resetRestrict("url"); yield check_autocomplete({ search: "foo +", @@ -219,7 +219,7 @@ add_task(function* test_special_searches() { { uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "tag" ] } ] }); - do_log_info("foo | -> is tag (change pref)"); + do_print("foo | -> is tag (change pref)"); changeRestrict("tag", "|"); yield check_autocomplete({ search: "foo |", @@ -229,7 +229,7 @@ add_task(function* test_special_searches() { { uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "tag" ] } ] }); - do_log_info("foo ~ -> is typed"); + do_print("foo ~ -> is typed"); resetRestrict("tag"); yield check_autocomplete({ search: "foo ~", @@ -237,7 +237,7 @@ add_task(function* test_special_searches() { { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] } ] }); - do_log_info("foo | -> is typed (change pref)"); + do_print("foo | -> is typed (change pref)"); changeRestrict("typed", "|"); yield check_autocomplete({ search: "foo |", @@ -246,7 +246,7 @@ add_task(function* test_special_searches() { }); // Test various pairs of special searches - do_log_info("foo ^ * -> history, is star"); + do_print("foo ^ * -> history, is star"); resetRestrict("typed"); yield check_autocomplete({ search: "foo ^ *", @@ -254,7 +254,7 @@ add_task(function* test_special_searches() { { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] } ] }); - do_log_info("foo ^ # -> history, in title"); + do_print("foo ^ # -> history, in title"); yield check_autocomplete({ search: "foo ^ #", matches: [ { uri: uri2, title: "foo.bar" }, @@ -263,7 +263,7 @@ add_task(function* test_special_searches() { { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] } ] }); - do_log_info("foo ^ @ -> history, in url"); + do_print("foo ^ @ -> history, in url"); yield check_autocomplete({ search: "foo ^ @", matches: [ { uri: uri3, title: "title" }, @@ -271,20 +271,20 @@ add_task(function* test_special_searches() { { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] } ] }); - do_log_info("foo ^ + -> history, is tag"); + do_print("foo ^ + -> history, is tag"); yield check_autocomplete({ search: "foo ^ +", matches: [ { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] } ] }); - do_log_info("foo ^ ~ -> history, is typed"); + do_print("foo ^ ~ -> history, is typed"); yield check_autocomplete({ search: "foo ^ ~", matches: [ { uri: uri4, title: "foo.bar" }, { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] } ] }); - do_log_info("foo * # -> is star, in title"); + do_print("foo * # -> is star, in title"); yield check_autocomplete({ search: "foo * #", matches: [ { uri: uri6, title: "foo.bar", style: [ "bookmark" ] }, @@ -295,7 +295,7 @@ add_task(function* test_special_searches() { { uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] } ] }); - do_log_info("foo * @ -> is star, in url"); + do_print("foo * @ -> is star, in url"); yield check_autocomplete({ search: "foo * @", matches: [ { uri: uri7, title: "title", style: [ "bookmark" ] }, @@ -304,7 +304,7 @@ add_task(function* test_special_searches() { { uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] } ] }); - do_log_info("foo * + -> same as +"); + do_print("foo * + -> same as +"); yield check_autocomplete({ search: "foo * +", matches: [ { uri: uri9, title: "title", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] }, @@ -313,13 +313,13 @@ add_task(function* test_special_searches() { { uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] } ] }); - do_log_info("foo * ~ -> is star, is typed"); + do_print("foo * ~ -> is star, is typed"); yield check_autocomplete({ search: "foo * ~", matches: [ { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] } ] }); - do_log_info("foo # @ -> in title, in url"); + do_print("foo # @ -> in title, in url"); yield check_autocomplete({ search: "foo # @", matches: [ { uri: uri4, title: "foo.bar" }, @@ -328,7 +328,7 @@ add_task(function* test_special_searches() { { uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "tag" ] } ] }); - do_log_info("foo # + -> in title, is tag"); + do_print("foo # + -> in title, is tag"); yield check_autocomplete({ search: "foo # +", matches: [ { uri: uri9, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] }, @@ -337,28 +337,28 @@ add_task(function* test_special_searches() { { uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "tag" ] } ] }); - do_log_info("foo # ~ -> in title, is typed"); + do_print("foo # ~ -> in title, is typed"); yield check_autocomplete({ search: "foo # ~", matches: [ { uri: uri4, title: "foo.bar" }, { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] } ] }); - do_log_info("foo @ + -> in url, is tag"); + do_print("foo @ + -> in url, is tag"); yield check_autocomplete({ search: "foo @ +", matches: [ { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] }, { uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "tag" ] } ] }); - do_log_info("foo @ ~ -> in url, is typed"); + do_print("foo @ ~ -> in url, is typed"); yield check_autocomplete({ search: "foo @ ~", matches: [ { uri: uri4, title: "foo.bar" }, { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] } ] }); - do_log_info("foo + ~ -> is tag, is typed"); + do_print("foo + ~ -> is tag, is typed"); yield check_autocomplete({ search: "foo + ~", matches: [ { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "tag" ] } ] @@ -369,7 +369,7 @@ add_task(function* test_special_searches() { Services.prefs.setBoolPref("browser.urlbar.autoFill", false); // Test default usage by setting certain browser.urlbar.suggest.* prefs - do_log_info("foo -> default history"); + do_print("foo -> default history"); setSuggestPrefsToFalse(); Services.prefs.setBoolPref("browser.urlbar.suggest.history", true); yield check_autocomplete({ @@ -381,7 +381,7 @@ add_task(function* test_special_searches() { { uri: uri11, title: "title", tags: ["foo.bar"], style: [ "tag" ] } ] }); - do_log_info("foo -> default history, is star"); + do_print("foo -> default history, is star"); setSuggestPrefsToFalse(); Services.prefs.setBoolPref("browser.urlbar.suggest.history", true); Services.prefs.setBoolPref("browser.urlbar.suggest.bookmark", true); @@ -399,7 +399,7 @@ add_task(function* test_special_searches() { { uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] } ] }); - do_log_info("foo -> default history, is star, is typed"); + do_print("foo -> default history, is star, is typed"); setSuggestPrefsToFalse(); Services.prefs.setBoolPref("browser.urlbar.suggest.history", true); Services.prefs.setBoolPref("browser.urlbar.suggest.history.onlyTyped", true); @@ -410,7 +410,7 @@ add_task(function* test_special_searches() { { uri: uri11, title: "title", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] } ] }); - do_log_info("foo -> is star"); + do_print("foo -> is star"); setSuggestPrefsToFalse(); Services.prefs.setBoolPref("browser.urlbar.suggest.history", false); Services.prefs.setBoolPref("browser.urlbar.suggest.bookmark", true); @@ -425,7 +425,7 @@ add_task(function* test_special_searches() { { uri: uri12, title: "foo.bar", tags: [ "foo.bar" ], style: [ "bookmark-tag" ] } ] }); - do_log_info("foo -> is star, is typed"); + do_print("foo -> is star, is typed"); setSuggestPrefsToFalse(); // only typed should be ignored Services.prefs.setBoolPref("browser.urlbar.suggest.history.onlyTyped", true); diff --git a/toolkit/components/places/tests/unifiedcomplete/test_swap_protocol.js b/toolkit/components/places/tests/unifiedcomplete/test_swap_protocol.js index ed11f7bdcb5..cf1d6c90c5e 100644 --- a/toolkit/components/places/tests/unifiedcomplete/test_swap_protocol.js +++ b/toolkit/components/places/tests/unifiedcomplete/test_swap_protocol.js @@ -42,106 +42,106 @@ add_task(function* test_swap_protocol() { Services.prefs.setBoolPref("browser.urlbar.autoFill", "false"); Services.prefs.setBoolPref("browser.urlbar.autoFill.searchEngines", false); - do_log_info("http://www.site matches all site"); + do_print("http://www.site matches all site"); yield check_autocomplete({ search: "http://www.site", matches: allMatches }); - do_log_info("http://site matches all site"); + do_print("http://site matches all site"); yield check_autocomplete({ search: "http://site", matches: allMatches }); - do_log_info("ftp://ftp.site matches itself"); + do_print("ftp://ftp.site matches itself"); yield check_autocomplete({ search: "ftp://ftp.site", matches: [ { uri: uri3, title: "title" } ] }); - do_log_info("ftp://site matches all site"); + do_print("ftp://site matches all site"); yield check_autocomplete({ search: "ftp://site", matches: allMatches }); - do_log_info("https://www.site matches all site"); + do_print("https://www.site matches all site"); yield check_autocomplete({ search: "https://www.site", matches: allMatches }); - do_log_info("https://site matches all site"); + do_print("https://site matches all site"); yield check_autocomplete({ search: "https://site", matches: allMatches }); - do_log_info("www.site matches all site"); + do_print("www.site matches all site"); yield check_autocomplete({ search: "www.site", matches: allMatches }); - do_log_info("w matches none of www."); + do_print("w matches none of www."); yield check_autocomplete({ search: "w", matches: [ { uri: uri7, title: "title" }, { uri: uri8, title: "title" } ] }); - do_log_info("http://w matches none of www."); + do_print("http://w matches none of www."); yield check_autocomplete({ search: "http://w", matches: [ { uri: uri7, title: "title" }, { uri: uri8, title: "title" } ] }); - do_log_info("http://w matches none of www."); + do_print("http://w matches none of www."); yield check_autocomplete({ search: "http://www.w", matches: [ { uri: uri7, title: "title" }, { uri: uri8, title: "title" } ] }); - do_log_info("ww matches none of www."); + do_print("ww matches none of www."); yield check_autocomplete({ search: "ww", matches: [ { uri: uri8, title: "title" } ] }); - do_log_info("ww matches none of www."); + do_print("ww matches none of www."); yield check_autocomplete({ search: "ww", matches: [ { uri: uri8, title: "title" } ] }); - do_log_info("http://ww matches none of www."); + do_print("http://ww matches none of www."); yield check_autocomplete({ search: "http://ww", matches: [ { uri: uri8, title: "title" } ] }); - do_log_info("http://www.ww matches none of www."); + do_print("http://www.ww matches none of www."); yield check_autocomplete({ search: "http://www.ww", matches: [ { uri: uri8, title: "title" } ] }); - do_log_info("www matches none of www."); + do_print("www matches none of www."); yield check_autocomplete({ search: "www", matches: [ { uri: uri8, title: "title" } ] }); - do_log_info("http://www matches none of www."); + do_print("http://www matches none of www."); yield check_autocomplete({ search: "http://www", matches: [ { uri: uri8, title: "title" } ] }); - do_log_info("http://www.www matches none of www."); + do_print("http://www.www matches none of www."); yield check_autocomplete({ search: "http://www.www", matches: [ { uri: uri8, title: "title" } ] diff --git a/toolkit/components/places/tests/unifiedcomplete/test_tabmatches.js b/toolkit/components/places/tests/unifiedcomplete/test_tabmatches.js index 53e35b57bd7..13185eaac51 100644 --- a/toolkit/components/places/tests/unifiedcomplete/test_tabmatches.js +++ b/toolkit/components/places/tests/unifiedcomplete/test_tabmatches.js @@ -23,7 +23,7 @@ add_task(function* test_tab_matches() { addOpenPages(uri3, 1); addOpenPages(uri4, 1); - do_log_info("two results, normal result is a tab match"); + do_print("two results, normal result is a tab match"); yield check_autocomplete({ search: "abc.com", searchParam: "enable-actions", @@ -31,7 +31,7 @@ add_task(function* test_tab_matches() { { uri: makeActionURI("switchtab", {url: "http://abc.com/"}), title: "ABC rocks", style: [ "action", "switchtab" ] } ] }); - do_log_info("three results, one tab match"); + do_print("three results, one tab match"); yield check_autocomplete({ search: "abc", searchParam: "enable-actions", @@ -40,7 +40,7 @@ add_task(function* test_tab_matches() { { uri: uri2, title: "xyz.net - we're better than ABC", style: [ "favicon" ] } ] }); - do_log_info("three results, both normal results are tab matches"); + do_print("three results, both normal results are tab matches"); addOpenPages(uri2, 1); yield check_autocomplete({ search: "abc", @@ -50,7 +50,7 @@ add_task(function* test_tab_matches() { { uri: makeActionURI("switchtab", {url: "http://xyz.net/"}), title: "xyz.net - we're better than ABC", style: [ "action", "switchtab" ] } ] }); - do_log_info("three results, both normal results are tab matches, one has multiple tabs"); + do_print("three results, both normal results are tab matches, one has multiple tabs"); addOpenPages(uri2, 5); yield check_autocomplete({ search: "abc", @@ -60,7 +60,7 @@ add_task(function* test_tab_matches() { { uri: makeActionURI("switchtab", {url: "http://xyz.net/"}), title: "xyz.net - we're better than ABC", style: [ "action", "switchtab" ] } ] }); - do_log_info("three results, no tab matches (disable-private-actions)"); + do_print("three results, no tab matches (disable-private-actions)"); yield check_autocomplete({ search: "abc", searchParam: "enable-actions disable-private-actions", @@ -69,7 +69,7 @@ add_task(function* test_tab_matches() { { uri: uri2, title: "xyz.net - we're better than ABC", style: [ "favicon" ] } ] }); - do_log_info("two results (actions disabled)"); + do_print("two results (actions disabled)"); yield check_autocomplete({ search: "abc", searchParam: "", @@ -77,7 +77,7 @@ add_task(function* test_tab_matches() { { uri: uri2, title: "xyz.net - we're better than ABC", style: [ "favicon" ] } ] }); - do_log_info("three results, no tab matches"); + do_print("three results, no tab matches"); removeOpenPages(uri1, 1); removeOpenPages(uri2, 6); yield check_autocomplete({ @@ -88,7 +88,7 @@ add_task(function* test_tab_matches() { { uri: uri2, title: "xyz.net - we're better than ABC", style: [ "favicon" ] } ] }); - do_log_info("tab match search with restriction character"); + do_print("tab match search with restriction character"); addOpenPages(uri1, 1); yield check_autocomplete({ search: gTabRestrictChar + " abc", @@ -97,7 +97,7 @@ add_task(function* test_tab_matches() { { uri: makeActionURI("switchtab", {url: "http://abc.com/"}), title: "ABC rocks", style: [ "action", "switchtab" ] } ] }); - do_log_info("tab match with not-addable pages"); + do_print("tab match with not-addable pages"); yield check_autocomplete({ search: "mozilla", searchParam: "enable-actions", @@ -105,7 +105,7 @@ add_task(function* test_tab_matches() { { uri: makeActionURI("switchtab", {url: "about:mozilla"}), title: "about:mozilla", style: [ "action", "switchtab" ] } ] }); - do_log_info("tab match with not-addable pages and restriction character"); + do_print("tab match with not-addable pages and restriction character"); yield check_autocomplete({ search: gTabRestrictChar + " mozilla", searchParam: "enable-actions", @@ -113,7 +113,7 @@ add_task(function* test_tab_matches() { { uri: makeActionURI("switchtab", {url: "about:mozilla"}), title: "about:mozilla", style: [ "action", "switchtab" ] } ] }); - do_log_info("tab match with not-addable pages and only restriction character"); + do_print("tab match with not-addable pages and only restriction character"); yield check_autocomplete({ search: gTabRestrictChar, searchParam: "enable-actions", diff --git a/toolkit/components/places/tests/unifiedcomplete/test_trimming.js b/toolkit/components/places/tests/unifiedcomplete/test_trimming.js index 3a0eda2f446..d7f32b3966e 100644 --- a/toolkit/components/places/tests/unifiedcomplete/test_trimming.js +++ b/toolkit/components/places/tests/unifiedcomplete/test_trimming.js @@ -3,7 +3,7 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ add_task(function* test_untrimmed_secure_www() { - do_log_info("Searching for untrimmed https://www entry"); + do_print("Searching for untrimmed https://www entry"); yield promiseAddVisits({ uri: NetUtil.newURI("https://www.mozilla.org/test/"), transition: TRANSITION_TYPED }); yield check_autocomplete({ @@ -15,7 +15,7 @@ add_task(function* test_untrimmed_secure_www() { }); add_task(function* test_untrimmed_secure_www_path() { - do_log_info("Searching for untrimmed https://www entry with path"); + do_print("Searching for untrimmed https://www entry with path"); yield promiseAddVisits({ uri: NetUtil.newURI("https://www.mozilla.org/test/"), transition: TRANSITION_TYPED }); yield check_autocomplete({ @@ -27,7 +27,7 @@ add_task(function* test_untrimmed_secure_www_path() { }); add_task(function* test_untrimmed_secure() { - do_log_info("Searching for untrimmed https:// entry"); + do_print("Searching for untrimmed https:// entry"); yield promiseAddVisits({ uri: NetUtil.newURI("https://mozilla.org/test/"), transition: TRANSITION_TYPED }); yield check_autocomplete({ @@ -39,7 +39,7 @@ add_task(function* test_untrimmed_secure() { }); add_task(function* test_untrimmed_secure_path() { - do_log_info("Searching for untrimmed https:// entry with path"); + do_print("Searching for untrimmed https:// entry with path"); yield promiseAddVisits({ uri: NetUtil.newURI("https://mozilla.org/test/"), transition: TRANSITION_TYPED }); yield check_autocomplete({ @@ -51,7 +51,7 @@ add_task(function* test_untrimmed_secure_path() { }); add_task(function* test_untrimmed_www() { - do_log_info("Searching for untrimmed http://www entry"); + do_print("Searching for untrimmed http://www entry"); yield promiseAddVisits({ uri: NetUtil.newURI("http://www.mozilla.org/test/"), transition: TRANSITION_TYPED }); yield check_autocomplete({ @@ -63,7 +63,7 @@ add_task(function* test_untrimmed_www() { }); add_task(function* test_untrimmed_www_path() { - do_log_info("Searching for untrimmed http://www entry with path"); + do_print("Searching for untrimmed http://www entry with path"); yield promiseAddVisits({ uri: NetUtil.newURI("http://www.mozilla.org/test/"), transition: TRANSITION_TYPED }); yield check_autocomplete({ @@ -75,7 +75,7 @@ add_task(function* test_untrimmed_www_path() { }); add_task(function* test_untrimmed_ftp() { - do_log_info("Searching for untrimmed ftp:// entry"); + do_print("Searching for untrimmed ftp:// entry"); yield promiseAddVisits({ uri: NetUtil.newURI("ftp://mozilla.org/test/"), transition: TRANSITION_TYPED }); yield check_autocomplete({ @@ -87,7 +87,7 @@ add_task(function* test_untrimmed_ftp() { }); add_task(function* test_untrimmed_ftp_path() { - do_log_info("Searching for untrimmed ftp:// entry with path"); + do_print("Searching for untrimmed ftp:// entry with path"); yield promiseAddVisits({ uri: NetUtil.newURI("ftp://mozilla.org/test/"), transition: TRANSITION_TYPED }); yield check_autocomplete({ @@ -99,7 +99,7 @@ add_task(function* test_untrimmed_ftp_path() { }); add_task(function* test_priority_1() { - do_log_info("Ensuring correct priority 1"); + do_print("Ensuring correct priority 1"); yield promiseAddVisits([{ uri: NetUtil.newURI("https://www.mozilla.org/test/"), transition: TRANSITION_TYPED }, { uri: NetUtil.newURI("https://mozilla.org/test/"), @@ -119,7 +119,7 @@ add_task(function* test_priority_1() { }); add_task(function* test_periority_2() { - do_log_info( "Ensuring correct priority 2"); + do_print( "Ensuring correct priority 2"); yield promiseAddVisits([{ uri: NetUtil.newURI("https://mozilla.org/test/"), transition: TRANSITION_TYPED }, { uri: NetUtil.newURI("ftp://mozilla.org/test/"), @@ -137,7 +137,7 @@ add_task(function* test_periority_2() { }); add_task(function* test_periority_3() { - do_log_info("Ensuring correct priority 3"); + do_print("Ensuring correct priority 3"); yield promiseAddVisits([{ uri: NetUtil.newURI("ftp://mozilla.org/test/"), transition: TRANSITION_TYPED }, { uri: NetUtil.newURI("http://www.mozilla.org/test/"), @@ -153,7 +153,7 @@ add_task(function* test_periority_3() { }); add_task(function* test_periority_4() { - do_log_info("Ensuring correct priority 4"); + do_print("Ensuring correct priority 4"); yield promiseAddVisits([{ uri: NetUtil.newURI("http://www.mozilla.org/test/"), transition: TRANSITION_TYPED }, { uri: NetUtil.newURI("http://mozilla.org/test/"), @@ -167,7 +167,7 @@ add_task(function* test_periority_4() { }); add_task(function* test_priority_5() { - do_log_info("Ensuring correct priority 5"); + do_print("Ensuring correct priority 5"); yield promiseAddVisits([{ uri: NetUtil.newURI("ftp://mozilla.org/test/"), transition: TRANSITION_TYPED }, { uri: NetUtil.newURI("ftp://www.mozilla.org/test/"), @@ -181,7 +181,7 @@ add_task(function* test_priority_5() { }); add_task(function* test_priority_6() { - do_log_info("Ensuring correct priority 6"); + do_print("Ensuring correct priority 6"); yield promiseAddVisits([{ uri: NetUtil.newURI("http://www.mozilla.org/test1/"), transition: TRANSITION_TYPED }, { uri: NetUtil.newURI("http://www.mozilla.org/test2/"), @@ -195,7 +195,7 @@ add_task(function* test_priority_6() { }); add_task(function* test_longer_domain() { - do_log_info("Ensuring longer domain can't match"); + do_print("Ensuring longer domain can't match"); // The .co should be preferred, but should not get the https from the .com. // The .co domain must be added later to activate the trigger bug. yield promiseAddVisits([{ uri: NetUtil.newURI("https://mozilla.com/"), @@ -214,7 +214,7 @@ add_task(function* test_longer_domain() { }); add_task(function* test_escaped_chars() { - do_log_info("Searching for URL with characters that are normally escaped"); + do_print("Searching for URL with characters that are normally escaped"); yield promiseAddVisits({ uri: NetUtil.newURI("https://www.mozilla.org/啊-test"), transition: TRANSITION_TYPED }); yield check_autocomplete({ @@ -226,7 +226,7 @@ add_task(function* test_escaped_chars() { }); add_task(function* test_unsecure_secure() { - do_log_info("Don't return unsecure URL when searching for secure ones"); + do_print("Don't return unsecure URL when searching for secure ones"); yield promiseAddVisits({ uri: NetUtil.newURI("http://test.moz.org/test/"), transition: TRANSITION_TYPED }); yield check_autocomplete({ @@ -238,7 +238,7 @@ add_task(function* test_unsecure_secure() { }); add_task(function* test_unsecure_secure_domain() { - do_log_info("Don't return unsecure domain when searching for secure ones"); + do_print("Don't return unsecure domain when searching for secure ones"); yield promiseAddVisits({ uri: NetUtil.newURI("http://test.moz.org/test/"), transition: TRANSITION_TYPED }); yield check_autocomplete({ @@ -250,7 +250,7 @@ add_task(function* test_unsecure_secure_domain() { }); add_task(function* test_untyped_www() { - do_log_info("Untyped is not accounted for www"); + do_print("Untyped is not accounted for www"); Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", false); yield promiseAddVisits({ uri: NetUtil.newURI("http://www.moz.org/test/") }); yield check_autocomplete({ @@ -262,7 +262,7 @@ add_task(function* test_untyped_www() { }); add_task(function* test_untyped_ftp() { - do_log_info("Untyped is not accounted for ftp"); + do_print("Untyped is not accounted for ftp"); Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", false); yield promiseAddVisits({ uri: NetUtil.newURI("ftp://moz.org/test/") }); yield check_autocomplete({ @@ -274,7 +274,7 @@ add_task(function* test_untyped_ftp() { }); add_task(function* test_untyped_secure() { - do_log_info("Untyped is not accounted for https"); + do_print("Untyped is not accounted for https"); Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", false); yield promiseAddVisits({ uri: NetUtil.newURI("https://moz.org/test/") }); yield check_autocomplete({ @@ -286,7 +286,7 @@ add_task(function* test_untyped_secure() { }); add_task(function* test_untyped_secure_www() { - do_log_info("Untyped is not accounted for https://www"); + do_print("Untyped is not accounted for https://www"); Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", false); yield promiseAddVisits({ uri: NetUtil.newURI("https://www.moz.org/test/") }); yield check_autocomplete({ diff --git a/toolkit/components/places/tests/unifiedcomplete/test_typed.js b/toolkit/components/places/tests/unifiedcomplete/test_typed.js index 930524b3655..bd28fc795d8 100644 --- a/toolkit/components/places/tests/unifiedcomplete/test_typed.js +++ b/toolkit/components/places/tests/unifiedcomplete/test_typed.js @@ -6,7 +6,7 @@ // ensure autocomplete is able to dinamically switch behavior. add_task(function* test_domain() { - do_log_info("Searching for domain should autoFill it"); + do_print("Searching for domain should autoFill it"); Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", false); yield promiseAddVisits(NetUtil.newURI("http://mozilla.org/link/")); yield check_autocomplete({ @@ -18,7 +18,7 @@ add_task(function* test_domain() { }); add_task(function* test_url() { - do_log_info("Searching for url should autoFill it"); + do_print("Searching for url should autoFill it"); Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", false); yield promiseAddVisits(NetUtil.newURI("http://mozilla.org/link/")); yield check_autocomplete({ @@ -32,7 +32,7 @@ add_task(function* test_url() { // Now do searches with typed behavior forced to true. add_task(function* test_untyped_domain() { - do_log_info("Searching for non-typed domain should not autoFill it"); + do_print("Searching for non-typed domain should not autoFill it"); yield promiseAddVisits(NetUtil.newURI("http://mozilla.org/link/")); yield check_autocomplete({ search: "moz", @@ -43,7 +43,7 @@ add_task(function* test_untyped_domain() { }); add_task(function* test_typed_domain() { - do_log_info("Searching for typed domain should autoFill it"); + do_print("Searching for typed domain should autoFill it"); yield promiseAddVisits({ uri: NetUtil.newURI("http://mozilla.org/typed/"), transition: TRANSITION_TYPED }); yield check_autocomplete({ @@ -55,7 +55,7 @@ add_task(function* test_typed_domain() { }); add_task(function* test_untyped_url() { - do_log_info("Searching for non-typed url should not autoFill it"); + do_print("Searching for non-typed url should not autoFill it"); yield promiseAddVisits(NetUtil.newURI("http://mozilla.org/link/")); yield check_autocomplete({ search: "mozilla.org/li", @@ -66,7 +66,7 @@ add_task(function* test_untyped_url() { }); add_task(function* test_typed_url() { - do_log_info("Searching for typed url should autoFill it"); + do_print("Searching for typed url should autoFill it"); yield promiseAddVisits({ uri: NetUtil.newURI("http://mozilla.org/link/"), transition: TRANSITION_TYPED }); yield check_autocomplete({ diff --git a/toolkit/components/places/tests/unifiedcomplete/test_visiturl.js b/toolkit/components/places/tests/unifiedcomplete/test_visiturl.js index af6ef4a4504..952a031c175 100644 --- a/toolkit/components/places/tests/unifiedcomplete/test_visiturl.js +++ b/toolkit/components/places/tests/unifiedcomplete/test_visiturl.js @@ -3,21 +3,21 @@ add_task(function*() { - do_log_info("visit url, no protocol"); + do_print("visit url, no protocol"); yield check_autocomplete({ search: "mozilla.org", searchParam: "enable-actions", matches: [ { uri: makeActionURI("visiturl", {url: "http://mozilla.org/", input: "mozilla.org"}), title: "http://mozilla.org/", style: [ "action", "visiturl" ] } ] }); - do_log_info("visit url, with protocol"); + do_print("visit url, with protocol"); yield check_autocomplete({ search: "https://mozilla.org", searchParam: "enable-actions", matches: [ { uri: makeActionURI("visiturl", {url: "https://mozilla.org/", input: "https://mozilla.org"}), title: "https://mozilla.org/", style: [ "action", "visiturl" ] } ] }); - do_log_info("visit url, about: protocol (no host)"); + do_print("visit url, about: protocol (no host)"); yield check_autocomplete({ search: "about:config", searchParam: "enable-actions", @@ -26,7 +26,7 @@ add_task(function*() { // This is distinct because of how we predict being able to url autofill via // host lookups. - do_log_info("visit url, host matching visited host but not visited url"); + do_print("visit url, host matching visited host but not visited url"); yield promiseAddVisits([ { uri: NetUtil.newURI("http://mozilla.org/wine/"), title: "Mozilla Wine", transition: TRANSITION_TYPED }, ]); @@ -37,7 +37,7 @@ add_task(function*() { }); // And hosts with no dot in them are special, due to requiring whitelisting. - do_log_info("visit url, host matching visited host but not visited url, non-whitelisted host"); + do_print("visit url, host matching visited host but not visited url, non-whitelisted host"); Services.search.addEngineWithDetails("MozSearch", "", "", "", "GET", "http://s.example.com/search"); let engine = Services.search.getEngineByName("MozSearch"); diff --git a/toolkit/components/places/tests/unifiedcomplete/test_word_boundary_search.js b/toolkit/components/places/tests/unifiedcomplete/test_word_boundary_search.js index 56828adb373..af43e3910a3 100644 --- a/toolkit/components/places/tests/unifiedcomplete/test_word_boundary_search.js +++ b/toolkit/components/places/tests/unifiedcomplete/test_word_boundary_search.js @@ -47,7 +47,7 @@ add_task(function* test_escape() { // match only on word boundaries Services.prefs.setIntPref("browser.urlbar.matchBehavior", 2); - do_log_info("Match 'match' at the beginning or after / or on a CamelCase"); + do_print("Match 'match' at the beginning or after / or on a CamelCase"); yield check_autocomplete({ search: "match", matches: [ { uri: uri1, title: "title1" }, @@ -56,7 +56,7 @@ add_task(function* test_escape() { { uri: uri10, title: "title1" } ] }); - do_log_info("Match 'dont' at the beginning or after /"); + do_print("Match 'dont' at the beginning or after /"); yield check_autocomplete({ search: "dont", matches: [ { uri: uri2, title: "title1" }, @@ -64,7 +64,7 @@ add_task(function* test_escape() { { uri: uri6, title: "title1", tags: [ "dontmatchme3" ], style: [ "bookmark-tag" ] } ] }); - do_log_info("Match 'match' at the beginning or after / or on a CamelCase"); + do_print("Match 'match' at the beginning or after / or on a CamelCase"); yield check_autocomplete({ search: "2", matches: [ { uri: uri3, title: "matchme2" }, @@ -73,7 +73,7 @@ add_task(function* test_escape() { { uri: uri6, title: "title1", tags: [ "dontmatchme3" ], style: [ "bookmark-tag" ] } ] }); - do_log_info("Match 't' at the beginning or after /"); + do_print("Match 't' at the beginning or after /"); yield check_autocomplete({ search: "t", matches: [ { uri: uri1, title: "title1" }, @@ -85,13 +85,13 @@ add_task(function* test_escape() { { uri: uri10, title: "title1" } ] }); - do_log_info("Match 'word' after many consecutive word boundaries"); + do_print("Match 'word' after many consecutive word boundaries"); yield check_autocomplete({ search: "word", matches: [ { uri: uri7, title: "!@#$%^&*()_+{}|:<>?word" } ] }); - do_log_info("Match a word boundary '/' for everything"); + do_print("Match a word boundary '/' for everything"); yield check_autocomplete({ search: "/", matches: [ { uri: uri1, title: "title1" }, @@ -106,50 +106,50 @@ add_task(function* test_escape() { { uri: uri10, title: "title1" } ] }); - do_log_info("Match word boundaries '()_+' that are among word boundaries"); + do_print("Match word boundaries '()_+' that are among word boundaries"); yield check_autocomplete({ search: "()_+", matches: [ { uri: uri7, title: "!@#$%^&*()_+{}|:<>?word" } ] }); - do_log_info("Katakana characters form a string, so match the beginning"); + do_print("Katakana characters form a string, so match the beginning"); yield check_autocomplete({ search: katakana[0], matches: [ { uri: uri8, title: katakana.join("") } ] }); /* - do_log_info("Middle of a katakana word shouldn't be matched"); + do_print("Middle of a katakana word shouldn't be matched"); yield check_autocomplete({ search: katakana[1], matches: [ ] }); */ - do_log_info("Ideographs are treated as words so 'nin' is one word"); + do_print("Ideographs are treated as words so 'nin' is one word"); yield check_autocomplete({ search: ideograph[0], matches: [ { uri: uri9, title: ideograph.join("") } ] }); - do_log_info("Ideographs are treated as words so 'ten' is another word"); + do_print("Ideographs are treated as words so 'ten' is another word"); yield check_autocomplete({ search: ideograph[1], matches: [ { uri: uri9, title: ideograph.join("") } ] }); - do_log_info("Ideographs are treated as words so 'do' is yet another word"); + do_print("Ideographs are treated as words so 'do' is yet another word"); yield check_autocomplete({ search: ideograph[2], matches: [ { uri: uri9, title: ideograph.join("") } ] }); - do_log_info("Extra negative assert that we don't match in the middle"); + do_print("Extra negative assert that we don't match in the middle"); yield check_autocomplete({ search: "ch", matches: [ ] }); - do_log_info("Don't match one character after a camel-case word boundary (bug 429498)"); + do_print("Don't match one character after a camel-case word boundary (bug 429498)"); yield check_autocomplete({ search: "atch", matches: [ ] diff --git a/toolkit/components/places/tests/unifiedcomplete/test_zero_frecency.js b/toolkit/components/places/tests/unifiedcomplete/test_zero_frecency.js index 20a2eb0facf..fa5c80ce201 100644 --- a/toolkit/components/places/tests/unifiedcomplete/test_zero_frecency.js +++ b/toolkit/components/places/tests/unifiedcomplete/test_zero_frecency.js @@ -5,7 +5,7 @@ // Ensure inline autocomplete doesn't return zero frecency pages. add_task(function* test_zzero_frec_domain() { - do_log_info("Searching for zero frecency domain should not autoFill it"); + do_print("Searching for zero frecency domain should not autoFill it"); Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", false); yield promiseAddVisits({ uri: NetUtil.newURI("http://mozilla.org/framed_link/"), transition: TRANSITION_FRAMED_LINK }); @@ -18,7 +18,7 @@ add_task(function* test_zzero_frec_domain() { }); add_task(function* test_zzero_frec_url() { - do_log_info("Searching for zero frecency url should not autoFill it"); + do_print("Searching for zero frecency url should not autoFill it"); Services.prefs.setBoolPref("browser.urlbar.autoFill.typed", false); yield promiseAddVisits({ uri: NetUtil.newURI("http://mozilla.org/framed_link/"), transition: TRANSITION_FRAMED_LINK }); diff --git a/toolkit/components/places/tests/unit/test_412132.js b/toolkit/components/places/tests/unit/test_412132.js index 305fd36ec36..f2aed44ad40 100644 --- a/toolkit/components/places/tests/unit/test_412132.js +++ b/toolkit/components/places/tests/unit/test_412132.js @@ -13,9 +13,9 @@ add_task(function changeuri_unvisited_bookmark() { - do_log_info("After changing URI of bookmark, frecency of bookmark's " + - "original URI should be zero if original URI is unvisited and " + - "no longer bookmarked."); + do_print("After changing URI of bookmark, frecency of bookmark's " + + "original URI should be zero if original URI is unvisited and " + + "no longer bookmarked."); const TEST_URI = NetUtil.newURI("http://example.com/1"); let id = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId, TEST_URI, @@ -23,14 +23,14 @@ add_task(function changeuri_unvisited_bookmark() "bookmark title"); yield promiseAsyncUpdates(); - do_log_info("Bookmarked => frecency of URI should be != 0"); + do_print("Bookmarked => frecency of URI should be != 0"); do_check_neq(frecencyForUrl(TEST_URI), 0); PlacesUtils.bookmarks.changeBookmarkURI(id, uri("http://example.com/2")); yield promiseAsyncUpdates(); - do_log_info("Unvisited URI no longer bookmarked => frecency should = 0"); + do_print("Unvisited URI no longer bookmarked => frecency should = 0"); do_check_eq(frecencyForUrl(TEST_URI), 0); remove_all_bookmarks(); @@ -39,8 +39,8 @@ add_task(function changeuri_unvisited_bookmark() add_task(function changeuri_visited_bookmark() { - do_log_info("After changing URI of bookmark, frecency of bookmark's " + - "original URI should not be zero if original URI is visited."); + do_print("After changing URI of bookmark, frecency of bookmark's " + + "original URI should not be zero if original URI is visited."); const TEST_URI = NetUtil.newURI("http://example.com/1"); let id = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId, TEST_URI, @@ -49,7 +49,7 @@ add_task(function changeuri_visited_bookmark() yield promiseAsyncUpdates(); - do_log_info("Bookmarked => frecency of URI should be != 0"); + do_print("Bookmarked => frecency of URI should be != 0"); do_check_neq(frecencyForUrl(TEST_URI), 0); yield promiseAddVisits(TEST_URI); @@ -60,7 +60,7 @@ add_task(function changeuri_visited_bookmark() yield promiseAsyncUpdates(); - do_log_info("*Visited* URI no longer bookmarked => frecency should != 0"); + do_print("*Visited* URI no longer bookmarked => frecency should != 0"); do_check_neq(frecencyForUrl(TEST_URI), 0); remove_all_bookmarks(); @@ -69,9 +69,9 @@ add_task(function changeuri_visited_bookmark() add_task(function changeuri_bookmark_still_bookmarked() { - do_log_info("After changing URI of bookmark, frecency of bookmark's " + - "original URI should not be zero if original URI is still " + - "bookmarked."); + do_print("After changing URI of bookmark, frecency of bookmark's " + + "original URI should not be zero if original URI is still " + + "bookmarked."); const TEST_URI = NetUtil.newURI("http://example.com/1"); let id1 = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId, TEST_URI, @@ -84,14 +84,14 @@ add_task(function changeuri_bookmark_still_bookmarked() yield promiseAsyncUpdates(); - do_log_info("Bookmarked => frecency of URI should be != 0"); + do_print("Bookmarked => frecency of URI should be != 0"); do_check_neq(frecencyForUrl(TEST_URI), 0); PlacesUtils.bookmarks.changeBookmarkURI(id1, uri("http://example.com/2")); yield promiseAsyncUpdates(); - do_log_info("URI still bookmarked => frecency should != 0"); + do_print("URI still bookmarked => frecency should != 0"); do_check_neq(frecencyForUrl(TEST_URI), 0); remove_all_bookmarks(); @@ -100,7 +100,7 @@ add_task(function changeuri_bookmark_still_bookmarked() add_task(function changeuri_nonexistent_bookmark() { - do_log_info("Changing the URI of a nonexistent bookmark should fail."); + do_print("Changing the URI of a nonexistent bookmark should fail."); function tryChange(itemId) { try { diff --git a/toolkit/components/places/tests/unit/test_PlacesSearchAutocompleteProvider.js b/toolkit/components/places/tests/unit/test_PlacesSearchAutocompleteProvider.js index fef11c34560..45de3556827 100644 --- a/toolkit/components/places/tests/unit/test_PlacesSearchAutocompleteProvider.js +++ b/toolkit/components/places/tests/unit/test_PlacesSearchAutocompleteProvider.js @@ -89,7 +89,7 @@ function promiseDefaultSearchEngine() { function promiseSearchTopic(expectedVerb) { let deferred = Promise.defer(); Services.obs.addObserver( function observe(subject, topic, verb) { - do_log_info("browser-search-engine-modified: " + verb); + do_print("browser-search-engine-modified: " + verb); if (verb == expectedVerb) { Services.obs.removeObserver(observe, "browser-search-engine-modified"); deferred.resolve(); diff --git a/toolkit/components/places/tests/unit/test_async_history_api.js b/toolkit/components/places/tests/unit/test_async_history_api.js index 41a00e6cec6..b4492a34242 100644 --- a/toolkit/components/places/tests/unit/test_async_history_api.js +++ b/toolkit/components/places/tests/unit/test_async_history_api.js @@ -69,7 +69,7 @@ function TitleChangedObserver(aURI, TitleChangedObserver.prototype = { __proto__: NavHistoryObserver.prototype, onTitleChanged(aURI, aTitle, aGUID) { - do_log_info("onTitleChanged(" + aURI.spec + ", " + aTitle + ", " + aGUID + ")"); + do_print("onTitleChanged(" + aURI.spec + ", " + aTitle + ", " + aGUID + ")"); if (!this.uri.equals(aURI)) { return; } @@ -106,9 +106,9 @@ VisitObserver.prototype = { aTransitionType, aGUID) { - do_log_info("onVisit(" + aURI.spec + ", " + aVisitId + ", " + aTime + - ", " + aSessionId + ", " + aReferringId + ", " + - aTransitionType + ", " + aGUID + ")"); + do_print("onVisit(" + aURI.spec + ", " + aVisitId + ", " + aTime + + ", " + aSessionId + ", " + aReferringId + ", " + + aTransitionType + ", " + aGUID + ")"); if (!this.uri.equals(aURI) || this.guid != aGUID) { return; } @@ -252,7 +252,7 @@ add_task(function* test_no_visits_throws() { (aPlace.uri ? "uri" : "no uri") + ", " + (aPlace.guid ? "guid" : "no guid") + ", " + (aPlace.visits ? "visits array" : "no visits array"); - do_log_info(str); + do_print(str); }; // Loop through every possible case. Note that we don't actually care about @@ -375,7 +375,7 @@ add_task(function* test_non_addable_uri_errors() { // NetUtil.newURI() can throw if e.g. our app knows about imap:// // but the account is not set up and so the URL is invalid for us. // Note this in the log but ignore as it's not the subject of this test. - do_log_info("Could not construct URI for '" + url + "'; ignoring"); + do_print("Could not construct URI for '" + url + "'; ignoring"); } }); @@ -384,7 +384,7 @@ add_task(function* test_non_addable_uri_errors() { do_throw("Unexpected success."); } for (let place of placesResult.errors) { - do_log_info("Checking '" + place.info.uri.spec + "'"); + do_print("Checking '" + place.info.uri.spec + "'"); do_check_eq(place.resultCode, Cr.NS_ERROR_INVALID_ARG); do_check_false(yield promiseIsURIVisited(place.info.uri)); } @@ -1046,7 +1046,7 @@ add_task(function* test_visit_notifies() { }); PlacesUtils.history.addObserver(visitObserver, false); let observer = function(aSubject, aTopic, aData) { - do_log_info("observe(" + aSubject + ", " + aTopic + ", " + aData + ")"); + do_print("observe(" + aSubject + ", " + aTopic + ", " + aData + ")"); do_check_true(aSubject instanceof Ci.nsIURI); do_check_true(aSubject.equals(place.uri)); @@ -1084,7 +1084,7 @@ add_task(function* test_callbacks_not_supplied() { // NetUtil.newURI() can throw if e.g. our app knows about imap:// // but the account is not set up and so the URL is invalid for us. // Note this in the log but ignore as it's not the subject of this test. - do_log_info("Could not construct URI for '" + url + "'; ignoring"); + do_print("Could not construct URI for '" + url + "'; ignoring"); } }); diff --git a/toolkit/components/places/tests/unit/test_isURIVisited.js b/toolkit/components/places/tests/unit/test_isURIVisited.js index 696b86fc377..a7f2f68500a 100644 --- a/toolkit/components/places/tests/unit/test_isURIVisited.js +++ b/toolkit/components/places/tests/unit/test_isURIVisited.js @@ -48,10 +48,10 @@ function step() .getService(Ci.mozIAsyncHistory); for (let scheme in SCHEMES) { - do_log_info("Testing scheme " + scheme); + do_print("Testing scheme " + scheme); for (let i = 0; i < TRANSITIONS.length; i++) { let transition = TRANSITIONS[i]; - do_log_info("With transition " + transition); + do_print("With transition " + transition); let uri = NetUtil.newURI(scheme + "mozilla.org/"); @@ -63,7 +63,7 @@ function step() handleError: function () {}, handleResult: function () {}, handleCompletion: function () { - do_log_info("Added visit to " + uri.spec); + do_print("Added visit to " + uri.spec); history.isURIVisited(uri, function (aURI, aIsVisited) { do_check_true(uri.equals(aURI)); diff --git a/toolkit/components/places/tests/unit/test_isvisited.js b/toolkit/components/places/tests/unit/test_isvisited.js index 866254028aa..630febbad22 100644 --- a/toolkit/components/places/tests/unit/test_isvisited.js +++ b/toolkit/components/places/tests/unit/test_isvisited.js @@ -59,7 +59,7 @@ add_task(function test_execute() // nsIIOService.newURI() can throw if e.g. our app knows about imap:// // but the account is not set up and so the URL is invalid for us. // Note this in the log but ignore as it's not the subject of this test. - do_log_info("Could not construct URI for '" + currentURL + "'; ignoring"); + do_print("Could not construct URI for '" + currentURL + "'; ignoring"); } if (cantAddUri) { try { diff --git a/toolkit/components/places/tests/unit/test_removeVisitsByTimeframe.js b/toolkit/components/places/tests/unit/test_removeVisitsByTimeframe.js index 15e84a542f0..3ae85350c78 100644 --- a/toolkit/components/places/tests/unit/test_removeVisitsByTimeframe.js +++ b/toolkit/components/places/tests/unit/test_removeVisitsByTimeframe.js @@ -16,23 +16,23 @@ function* cleanup() { } add_task(function remove_visits_outside_unbookmarked_uri() { - do_log_info("*** TEST: Remove some visits outside valid timeframe from an unbookmarked URI"); + do_print("*** TEST: Remove some visits outside valid timeframe from an unbookmarked URI"); - do_log_info("Add 10 visits for the URI from way in the past."); + do_print("Add 10 visits for the URI from way in the past."); let visits = []; for (let i = 0; i < 10; i++) { visits.push({ uri: TEST_URI, visitDate: NOW - 1000 - i }); } yield promiseAddVisits(visits); - do_log_info("Remove visits using timerange outside the URI's visits."); + do_print("Remove visits using timerange outside the URI's visits."); PlacesUtils.history.removeVisitsByTimeframe(NOW - 10, NOW); yield promiseAsyncUpdates(); - do_log_info("URI should still exist in moz_places."); + do_print("URI should still exist in moz_places."); do_check_true(page_in_database(TEST_URI.spec)); - do_log_info("Run a history query and check that all visits still exist."); + do_print("Run a history query and check that all visits still exist."); let query = PlacesUtils.history.getNewQuery(); let opts = PlacesUtils.history.getNewQueryOptions(); opts.resultType = opts.RESULTS_AS_VISIT; @@ -46,40 +46,40 @@ add_task(function remove_visits_outside_unbookmarked_uri() { } root.containerOpen = false; - do_log_info("asyncHistory.isURIVisited should return true."); + do_print("asyncHistory.isURIVisited should return true."); do_check_true(yield promiseIsURIVisited(TEST_URI)); yield promiseAsyncUpdates(); - do_log_info("Frecency should be positive.") + do_print("Frecency should be positive.") do_check_true(frecencyForUrl(TEST_URI) > 0); yield cleanup(); }); add_task(function remove_visits_outside_bookmarked_uri() { - do_log_info("*** TEST: Remove some visits outside valid timeframe from a bookmarked URI"); + do_print("*** TEST: Remove some visits outside valid timeframe from a bookmarked URI"); - do_log_info("Add 10 visits for the URI from way in the past."); + do_print("Add 10 visits for the URI from way in the past."); let visits = []; for (let i = 0; i < 10; i++) { visits.push({ uri: TEST_URI, visitDate: NOW - 1000 - i }); } yield promiseAddVisits(visits); - do_log_info("Bookmark the URI."); + do_print("Bookmark the URI."); PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId, TEST_URI, PlacesUtils.bookmarks.DEFAULT_INDEX, "bookmark title"); yield promiseAsyncUpdates(); - do_log_info("Remove visits using timerange outside the URI's visits."); + do_print("Remove visits using timerange outside the URI's visits."); PlacesUtils.history.removeVisitsByTimeframe(NOW - 10, NOW); yield promiseAsyncUpdates(); - do_log_info("URI should still exist in moz_places."); + do_print("URI should still exist in moz_places."); do_check_true(page_in_database(TEST_URI.spec)); - do_log_info("Run a history query and check that all visits still exist."); + do_print("Run a history query and check that all visits still exist."); let query = PlacesUtils.history.getNewQuery(); let opts = PlacesUtils.history.getNewQueryOptions(); opts.resultType = opts.RESULTS_AS_VISIT; @@ -93,34 +93,34 @@ add_task(function remove_visits_outside_bookmarked_uri() { } root.containerOpen = false; - do_log_info("asyncHistory.isURIVisited should return true."); + do_print("asyncHistory.isURIVisited should return true."); do_check_true(yield promiseIsURIVisited(TEST_URI)); yield promiseAsyncUpdates(); - do_log_info("Frecency should be positive.") + do_print("Frecency should be positive.") do_check_true(frecencyForUrl(TEST_URI) > 0); yield cleanup(); }); add_task(function remove_visits_unbookmarked_uri() { - do_log_info("*** TEST: Remove some visits from an unbookmarked URI"); + do_print("*** TEST: Remove some visits from an unbookmarked URI"); - do_log_info("Add 10 visits for the URI from now to 9 usecs in the past."); + do_print("Add 10 visits for the URI from now to 9 usecs in the past."); let visits = []; for (let i = 0; i < 10; i++) { visits.push({ uri: TEST_URI, visitDate: NOW - i }); } yield promiseAddVisits(visits); - do_log_info("Remove the 5 most recent visits."); + do_print("Remove the 5 most recent visits."); PlacesUtils.history.removeVisitsByTimeframe(NOW - 4, NOW); yield promiseAsyncUpdates(); - do_log_info("URI should still exist in moz_places."); + do_print("URI should still exist in moz_places."); do_check_true(page_in_database(TEST_URI.spec)); - do_log_info("Run a history query and check that only the older 5 visits still exist."); + do_print("Run a history query and check that only the older 5 visits still exist."); let query = PlacesUtils.history.getNewQuery(); let opts = PlacesUtils.history.getNewQueryOptions(); opts.resultType = opts.RESULTS_AS_VISIT; @@ -134,40 +134,40 @@ add_task(function remove_visits_unbookmarked_uri() { } root.containerOpen = false; - do_log_info("asyncHistory.isURIVisited should return true."); + do_print("asyncHistory.isURIVisited should return true."); do_check_true(yield promiseIsURIVisited(TEST_URI)); yield promiseAsyncUpdates(); - do_log_info("Frecency should be positive.") + do_print("Frecency should be positive.") do_check_true(frecencyForUrl(TEST_URI) > 0); yield cleanup(); }); add_task(function remove_visits_bookmarked_uri() { - do_log_info("*** TEST: Remove some visits from a bookmarked URI"); + do_print("*** TEST: Remove some visits from a bookmarked URI"); - do_log_info("Add 10 visits for the URI from now to 9 usecs in the past."); + do_print("Add 10 visits for the URI from now to 9 usecs in the past."); let visits = []; for (let i = 0; i < 10; i++) { visits.push({ uri: TEST_URI, visitDate: NOW - i }); } yield promiseAddVisits(visits); - do_log_info("Bookmark the URI."); + do_print("Bookmark the URI."); PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId, TEST_URI, PlacesUtils.bookmarks.DEFAULT_INDEX, "bookmark title"); yield promiseAsyncUpdates(); - do_log_info("Remove the 5 most recent visits."); + do_print("Remove the 5 most recent visits."); PlacesUtils.history.removeVisitsByTimeframe(NOW - 4, NOW); yield promiseAsyncUpdates(); - do_log_info("URI should still exist in moz_places."); + do_print("URI should still exist in moz_places."); do_check_true(page_in_database(TEST_URI.spec)); - do_log_info("Run a history query and check that only the older 5 visits still exist."); + do_print("Run a history query and check that only the older 5 visits still exist."); let query = PlacesUtils.history.getNewQuery(); let opts = PlacesUtils.history.getNewQueryOptions(); opts.resultType = opts.RESULTS_AS_VISIT; @@ -181,34 +181,34 @@ add_task(function remove_visits_bookmarked_uri() { } root.containerOpen = false; - do_log_info("asyncHistory.isURIVisited should return true."); + do_print("asyncHistory.isURIVisited should return true."); do_check_true(yield promiseIsURIVisited(TEST_URI)); yield promiseAsyncUpdates() - do_log_info("Frecency should be positive.") + do_print("Frecency should be positive.") do_check_true(frecencyForUrl(TEST_URI) > 0); yield cleanup(); }); add_task(function remove_all_visits_unbookmarked_uri() { - do_log_info("*** TEST: Remove all visits from an unbookmarked URI"); + do_print("*** TEST: Remove all visits from an unbookmarked URI"); - do_log_info("Add some visits for the URI."); + do_print("Add some visits for the URI."); let visits = []; for (let i = 0; i < 10; i++) { visits.push({ uri: TEST_URI, visitDate: NOW - i }); } yield promiseAddVisits(visits); - do_log_info("Remove all visits."); + do_print("Remove all visits."); PlacesUtils.history.removeVisitsByTimeframe(NOW - 10, NOW); yield promiseAsyncUpdates(); - do_log_info("URI should no longer exist in moz_places."); + do_print("URI should no longer exist in moz_places."); do_check_false(page_in_database(TEST_URI.spec)); - do_log_info("Run a history query and check that no visits exist."); + do_print("Run a history query and check that no visits exist."); let query = PlacesUtils.history.getNewQuery(); let opts = PlacesUtils.history.getNewQueryOptions(); opts.resultType = opts.RESULTS_AS_VISIT; @@ -218,29 +218,29 @@ add_task(function remove_all_visits_unbookmarked_uri() { do_check_eq(root.childCount, 0); root.containerOpen = false; - do_log_info("asyncHistory.isURIVisited should return false."); + do_print("asyncHistory.isURIVisited should return false."); do_check_false(yield promiseIsURIVisited(TEST_URI)); yield cleanup(); }); add_task(function remove_all_visits_unbookmarked_place_uri() { - do_log_info("*** TEST: Remove all visits from an unbookmarked place: URI"); - do_log_info("Add some visits for the URI."); + do_print("*** TEST: Remove all visits from an unbookmarked place: URI"); + do_print("Add some visits for the URI."); let visits = []; for (let i = 0; i < 10; i++) { visits.push({ uri: PLACE_URI, visitDate: NOW - i }); } yield promiseAddVisits(visits); - do_log_info("Remove all visits."); + do_print("Remove all visits."); PlacesUtils.history.removeVisitsByTimeframe(NOW - 10, NOW); yield promiseAsyncUpdates(); - do_log_info("URI should still exist in moz_places."); + do_print("URI should still exist in moz_places."); do_check_true(page_in_database(PLACE_URI.spec)); - do_log_info("Run a history query and check that no visits exist."); + do_print("Run a history query and check that no visits exist."); let query = PlacesUtils.history.getNewQuery(); let opts = PlacesUtils.history.getNewQueryOptions(); opts.resultType = opts.RESULTS_AS_VISIT; @@ -250,40 +250,40 @@ add_task(function remove_all_visits_unbookmarked_place_uri() { do_check_eq(root.childCount, 0); root.containerOpen = false; - do_log_info("asyncHistory.isURIVisited should return false."); + do_print("asyncHistory.isURIVisited should return false."); do_check_false(yield promiseIsURIVisited(PLACE_URI)); yield promiseAsyncUpdates(); - do_log_info("Frecency should be zero.") + do_print("Frecency should be zero.") do_check_eq(frecencyForUrl(PLACE_URI.spec), 0); yield cleanup(); }); add_task(function remove_all_visits_bookmarked_uri() { - do_log_info("*** TEST: Remove all visits from a bookmarked URI"); + do_print("*** TEST: Remove all visits from a bookmarked URI"); - do_log_info("Add some visits for the URI."); + do_print("Add some visits for the URI."); let visits = []; for (let i = 0; i < 10; i++) { visits.push({ uri: TEST_URI, visitDate: NOW - i }); } yield promiseAddVisits(visits); - do_log_info("Bookmark the URI."); + do_print("Bookmark the URI."); PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId, TEST_URI, PlacesUtils.bookmarks.DEFAULT_INDEX, "bookmark title"); yield promiseAsyncUpdates(); - do_log_info("Remove all visits."); + do_print("Remove all visits."); PlacesUtils.history.removeVisitsByTimeframe(NOW - 10, NOW); yield promiseAsyncUpdates(); - do_log_info("URI should still exist in moz_places."); + do_print("URI should still exist in moz_places."); do_check_true(page_in_database(TEST_URI.spec)); - do_log_info("Run a history query and check that no visits exist."); + do_print("Run a history query and check that no visits exist."); let query = PlacesUtils.history.getNewQuery(); let opts = PlacesUtils.history.getNewQueryOptions(); opts.resultType = opts.RESULTS_AS_VISIT; @@ -293,35 +293,35 @@ add_task(function remove_all_visits_bookmarked_uri() { do_check_eq(root.childCount, 0); root.containerOpen = false; - do_log_info("asyncHistory.isURIVisited should return false."); + do_print("asyncHistory.isURIVisited should return false."); do_check_false(yield promiseIsURIVisited(TEST_URI)); - do_log_info("nsINavBookmarksService.isBookmarked should return true."); + do_print("nsINavBookmarksService.isBookmarked should return true."); do_check_true(PlacesUtils.bookmarks.isBookmarked(TEST_URI)); yield promiseAsyncUpdates(); - do_log_info("Frecency should be negative.") + do_print("Frecency should be negative.") do_check_true(frecencyForUrl(TEST_URI) < 0); yield cleanup(); }); add_task(function remove_all_visits_bookmarked_uri() { - do_log_info("*** TEST: Remove some visits from a zero frecency URI retains zero frecency"); + do_print("*** TEST: Remove some visits from a zero frecency URI retains zero frecency"); - do_log_info("Add some visits for the URI."); + do_print("Add some visits for the URI."); yield promiseAddVisits([{ uri: TEST_URI, transition: TRANSITION_FRAMED_LINK, visitDate: (NOW - 86400000000) }, { uri: TEST_URI, transition: TRANSITION_FRAMED_LINK, visitDate: NOW }]); - do_log_info("Remove newer visit."); + do_print("Remove newer visit."); PlacesUtils.history.removeVisitsByTimeframe(NOW - 10, NOW); yield promiseAsyncUpdates(); - do_log_info("URI should still exist in moz_places."); + do_print("URI should still exist in moz_places."); do_check_true(page_in_database(TEST_URI.spec)); - do_log_info("Frecency should be zero.") + do_print("Frecency should be zero.") do_check_eq(frecencyForUrl(TEST_URI), 0); yield cleanup(); diff --git a/toolkit/components/places/tests/unit/test_telemetry.js b/toolkit/components/places/tests/unit/test_telemetry.js index b286e41fdb7..0c0d13da4c2 100644 --- a/toolkit/components/places/tests/unit/test_telemetry.js +++ b/toolkit/components/places/tests/unit/test_telemetry.js @@ -117,7 +117,7 @@ add_task(function test_execute() yield promiseTopicObserved("places-maintenance-finished"); for (let histogramId in histograms) { - do_log_info("checking histogram " + histogramId); + do_print("checking histogram " + histogramId); let validate = histograms[histogramId]; let snapshot = Services.telemetry.getHistogramById(histogramId).snapshot(); validate(snapshot.sum); diff --git a/toolkit/components/places/tests/unit/test_update_frecency_after_delete.js b/toolkit/components/places/tests/unit/test_update_frecency_after_delete.js index 5d0835e62a2..88610bccba2 100644 --- a/toolkit/components/places/tests/unit/test_update_frecency_after_delete.js +++ b/toolkit/components/places/tests/unit/test_update_frecency_after_delete.js @@ -14,8 +14,8 @@ add_test(function removed_bookmark() { - do_log_info("After removing bookmark, frecency of bookmark's URI should be " + - "zero if URI is unvisited and no longer bookmarked."); + do_print("After removing bookmark, frecency of bookmark's URI should be " + + "zero if URI is unvisited and no longer bookmarked."); const TEST_URI = NetUtil.newURI("http://example.com/1"); let id = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId, TEST_URI, @@ -23,14 +23,14 @@ add_test(function removed_bookmark() "bookmark title"); promiseAsyncUpdates().then(function () { - do_log_info("Bookmarked => frecency of URI should be != 0"); + do_print("Bookmarked => frecency of URI should be != 0"); do_check_neq(frecencyForUrl(TEST_URI), 0); PlacesUtils.bookmarks.removeItem(id); promiseAsyncUpdates().then(function () { - do_log_info("Unvisited URI no longer bookmarked => frecency should = 0"); + do_print("Unvisited URI no longer bookmarked => frecency should = 0"); do_check_eq(frecencyForUrl(TEST_URI), 0); remove_all_bookmarks(); @@ -41,8 +41,8 @@ add_test(function removed_bookmark() add_test(function removed_but_visited_bookmark() { - do_log_info("After removing bookmark, frecency of bookmark's URI should " + - "not be zero if URI is visited."); + do_print("After removing bookmark, frecency of bookmark's URI should " + + "not be zero if URI is visited."); const TEST_URI = NetUtil.newURI("http://example.com/1"); let id = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId, TEST_URI, @@ -50,7 +50,7 @@ add_test(function removed_but_visited_bookmark() "bookmark title"); promiseAsyncUpdates().then(function () { - do_log_info("Bookmarked => frecency of URI should be != 0"); + do_print("Bookmarked => frecency of URI should be != 0"); do_check_neq(frecencyForUrl(TEST_URI), 0); promiseAddVisits(TEST_URI).then(function () { @@ -58,7 +58,7 @@ add_test(function removed_but_visited_bookmark() promiseAsyncUpdates().then(function () { - do_log_info("*Visited* URI no longer bookmarked => frecency should != 0"); + do_print("*Visited* URI no longer bookmarked => frecency should != 0"); do_check_neq(frecencyForUrl(TEST_URI), 0); remove_all_bookmarks(); @@ -70,8 +70,8 @@ add_test(function removed_but_visited_bookmark() add_test(function remove_bookmark_still_bookmarked() { - do_log_info("After removing bookmark, frecency of bookmark's URI should ", - "not be zero if URI is still bookmarked."); + do_print("After removing bookmark, frecency of bookmark's URI should " + + "not be zero if URI is still bookmarked."); const TEST_URI = NetUtil.newURI("http://example.com/1"); let id1 = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId, TEST_URI, @@ -83,14 +83,14 @@ add_test(function remove_bookmark_still_bookmarked() "bookmark 2 title"); promiseAsyncUpdates().then(function () { - do_log_info("Bookmarked => frecency of URI should be != 0"); + do_print("Bookmarked => frecency of URI should be != 0"); do_check_neq(frecencyForUrl(TEST_URI), 0); PlacesUtils.bookmarks.removeItem(id1); promiseAsyncUpdates().then(function () { - do_log_info("URI still bookmarked => frecency should != 0"); + do_print("URI still bookmarked => frecency should != 0"); do_check_neq(frecencyForUrl(TEST_URI), 0); remove_all_bookmarks(); @@ -101,8 +101,8 @@ add_test(function remove_bookmark_still_bookmarked() add_test(function cleared_parent_of_visited_bookmark() { - do_log_info("After removing all children from bookmark's parent, frecency " + - "of bookmark's URI should not be zero if URI is visited."); + do_print("After removing all children from bookmark's parent, frecency " + + "of bookmark's URI should not be zero if URI is visited."); const TEST_URI = NetUtil.newURI("http://example.com/1"); let id = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId, TEST_URI, @@ -110,7 +110,7 @@ add_test(function cleared_parent_of_visited_bookmark() "bookmark title"); promiseAsyncUpdates().then(function () { - do_log_info("Bookmarked => frecency of URI should be != 0"); + do_print("Bookmarked => frecency of URI should be != 0"); do_check_neq(frecencyForUrl(TEST_URI), 0); promiseAddVisits(TEST_URI).then(function () { @@ -118,7 +118,7 @@ add_test(function cleared_parent_of_visited_bookmark() promiseAsyncUpdates().then(function () { - do_log_info("*Visited* URI no longer bookmarked => frecency should != 0"); + do_print("*Visited* URI no longer bookmarked => frecency should != 0"); do_check_neq(frecencyForUrl(TEST_URI), 0); remove_all_bookmarks(); @@ -130,9 +130,9 @@ add_test(function cleared_parent_of_visited_bookmark() add_test(function cleared_parent_of_bookmark_still_bookmarked() { - do_log_info("After removing all children from bookmark's parent, frecency " + - "of bookmark's URI should not be zero if URI is still " + - "bookmarked."); + do_print("After removing all children from bookmark's parent, frecency " + + "of bookmark's URI should not be zero if URI is still " + + "bookmarked."); const TEST_URI = NetUtil.newURI("http://example.com/1"); let id1 = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.toolbarFolderId, TEST_URI, @@ -145,7 +145,7 @@ add_test(function cleared_parent_of_bookmark_still_bookmarked() "bookmark 2 title"); promiseAsyncUpdates().then(function () { - do_log_info("Bookmarked => frecency of URI should be != 0"); + do_print("Bookmarked => frecency of URI should be != 0"); do_check_neq(frecencyForUrl(TEST_URI), 0); PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId); diff --git a/toolkit/components/places/tests/unit/test_utils_backups_create.js b/toolkit/components/places/tests/unit/test_utils_backups_create.js index 98b791cec24..20539304260 100644 --- a/toolkit/components/places/tests/unit/test_utils_backups_create.js +++ b/toolkit/components/places/tests/unit/test_utils_backups_create.js @@ -42,7 +42,7 @@ add_task(function () { let backupFile = bookmarksBackupDir.clone(); backupFile.append(backupFilename); backupFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("0666", 8)); - do_log_info("Creating fake backup " + backupFile.leafName); + do_print("Creating fake backup " + backupFile.leafName); if (!backupFile.exists()) do_throw("Unable to create fake backup " + backupFile.leafName); } From 4443ecdcacd363eec36ed6e41045ecf858cb2658 Mon Sep 17 00:00:00 2001 From: Brian Nicholson Date: Fri, 30 Jan 2015 16:53:27 -0800 Subject: [PATCH 094/101] Bug 1126514 - Add tile IDs to Fennec tiles. r=mfinkle --- mobile/locales/en-US/chrome/region.properties | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mobile/locales/en-US/chrome/region.properties b/mobile/locales/en-US/chrome/region.properties index 5deefc05645..546930c4bdf 100644 --- a/mobile/locales/en-US/chrome/region.properties +++ b/mobile/locales/en-US/chrome/region.properties @@ -54,15 +54,19 @@ browser.suggestedsites.list.3=fxsupport browser.suggestedsites.mozilla.title=The Mozilla Project browser.suggestedsites.mozilla.url=https://www.mozilla.org/en-US/ browser.suggestedsites.mozilla.bgcolor=#ce4e41 +browser.suggestedsites.mozilla.trackingid=632 browser.suggestedsites.fxmarketplace.title=Firefox Marketplace browser.suggestedsites.fxmarketplace.url=https://marketplace.firefox.com/ browser.suggestedsites.fxmarketplace.bgcolor=#0096dd +browser.suggestedsites.fxmarketplace.trackingid=629 browser.suggestedsites.fxaddons.title=Add-ons: Customize Firefox browser.suggestedsites.fxaddons.url=https://addons.mozilla.org/en-US/android/ browser.suggestedsites.fxaddons.bgcolor=#62be06 +browser.suggestedsites.fxaddons.trackingid=630 browser.suggestedsites.fxsupport.title=Firefox Help and Support browser.suggestedsites.fxsupport.url=https://support.mozilla.org/en-US/products/mobile browser.suggestedsites.fxsupport.bgcolor=#f37c00 +browser.suggestedsites.fxsupport.trackingid=631 From f1e6f804c9166c2c80b201ef67506b9e947f792a Mon Sep 17 00:00:00 2001 From: Phil Ringnalda Date: Fri, 30 Jan 2015 19:13:32 -0800 Subject: [PATCH 095/101] Back out cebdafba3a85 (bug 1126514) for robocop bustage --- mobile/locales/en-US/chrome/region.properties | 4 ---- 1 file changed, 4 deletions(-) diff --git a/mobile/locales/en-US/chrome/region.properties b/mobile/locales/en-US/chrome/region.properties index 546930c4bdf..5deefc05645 100644 --- a/mobile/locales/en-US/chrome/region.properties +++ b/mobile/locales/en-US/chrome/region.properties @@ -54,19 +54,15 @@ browser.suggestedsites.list.3=fxsupport browser.suggestedsites.mozilla.title=The Mozilla Project browser.suggestedsites.mozilla.url=https://www.mozilla.org/en-US/ browser.suggestedsites.mozilla.bgcolor=#ce4e41 -browser.suggestedsites.mozilla.trackingid=632 browser.suggestedsites.fxmarketplace.title=Firefox Marketplace browser.suggestedsites.fxmarketplace.url=https://marketplace.firefox.com/ browser.suggestedsites.fxmarketplace.bgcolor=#0096dd -browser.suggestedsites.fxmarketplace.trackingid=629 browser.suggestedsites.fxaddons.title=Add-ons: Customize Firefox browser.suggestedsites.fxaddons.url=https://addons.mozilla.org/en-US/android/ browser.suggestedsites.fxaddons.bgcolor=#62be06 -browser.suggestedsites.fxaddons.trackingid=630 browser.suggestedsites.fxsupport.title=Firefox Help and Support browser.suggestedsites.fxsupport.url=https://support.mozilla.org/en-US/products/mobile browser.suggestedsites.fxsupport.bgcolor=#f37c00 -browser.suggestedsites.fxsupport.trackingid=631 From 262d24d4121e14197525118612a922eacf138032 Mon Sep 17 00:00:00 2001 From: Jet Villegas Date: Fri, 30 Jan 2015 21:59:35 -0800 Subject: [PATCH 096/101] Bug 1125621 - Remove https://* restriction from the YouTube MSE check. r=kinetik --- dom/media/mediasource/MediaSource.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/dom/media/mediasource/MediaSource.cpp b/dom/media/mediasource/MediaSource.cpp index 328f3dbad3c..1021e09427f 100644 --- a/dom/media/mediasource/MediaSource.cpp +++ b/dom/media/mediasource/MediaSource.cpp @@ -341,19 +341,14 @@ MediaSource::Enabled(JSContext* cx, JSObject* aGlobal) } // We want to restrict to YouTube only. - // We define that as the origin being https://*.youtube.com. - // We also support https://*.youtube-nocookie.com. + // We define that as the origin being *.youtube.com. + // We also support *.youtube-nocookie.com nsIPrincipal* principal = nsContentUtils::ObjectPrincipal(global); nsCOMPtr uri; if (NS_FAILED(principal->GetURI(getter_AddRefs(uri))) || !uri) { return false; } - bool isHttps = false; - if (NS_FAILED(uri->SchemeIs("https", &isHttps)) || !isHttps) { - return false; - } - nsCOMPtr tldServ = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID); NS_ENSURE_TRUE(tldServ, false); From bcda60a836a21e5488ce7122be0ce5cd3fa48fa0 Mon Sep 17 00:00:00 2001 From: Xidorn Quan Date: Sat, 31 Jan 2015 18:17:12 +1100 Subject: [PATCH 097/101] Bug 569334 part 1 - Support getting font info in content query. r=masayuki,jfkthame,smaug --- dom/events/ContentEventHandler.cpp | 196 +++++++++++++++++++++++++++-- dom/events/ContentEventHandler.h | 18 +++ ipc/glue/IPCMessageUtils.h | 6 + widget/EventForwards.h | 3 + widget/FontRange.h | 27 ++++ widget/TextEvents.h | 12 ++ widget/moz.build | 1 + widget/nsGUIEventIPC.h | 26 +++- 8 files changed, 281 insertions(+), 8 deletions(-) create mode 100644 widget/FontRange.h diff --git a/dom/events/ContentEventHandler.cpp b/dom/events/ContentEventHandler.cpp index 93427c75e76..f5e83a2e89b 100644 --- a/dom/events/ContentEventHandler.cpp +++ b/dom/events/ContentEventHandler.cpp @@ -314,6 +314,17 @@ ContentEventHandler::GetNativeTextLength(nsIContent* aContent, return GetTextLength(aContent, LINE_BREAK_TYPE_NATIVE, aMaxLength); } +static inline uint32_t +GetBRLength(LineBreakType aLineBreakType) +{ +#if defined(XP_WIN) + // Length of \r\n + return (aLineBreakType == LINE_BREAK_TYPE_NATIVE) ? 2 : 1; +#else + return 1; +#endif +} + /* static */ uint32_t ContentEventHandler::GetTextLength(nsIContent* aContent, LineBreakType aLineBreakType, @@ -344,12 +355,7 @@ ContentEventHandler::GetTextLength(nsIContent* aContent, uint32_t length = std::min(text->GetLength(), aMaxLength); return length + textLengthDifference; } else if (IsContentBR(aContent)) { -#if defined(XP_WIN) - // Length of \r\n - return (aLineBreakType == LINE_BREAK_TYPE_NATIVE) ? 2 : 1; -#else - return 1; -#endif + return GetBRLength(aLineBreakType); } return 0; } @@ -393,7 +399,6 @@ static nsresult GenerateFlatTextContent(nsRange* aRange, return NS_OK; } - nsAutoString tmpStr; for (; !iter->IsDone(); iter->Next()) { nsINode* node = iter->GetCurrentNode(); if (!node) { @@ -423,6 +428,171 @@ static nsresult GenerateFlatTextContent(nsRange* aRange, return NS_OK; } +static FontRange* +AppendFontRange(nsTArray& aFontRanges, uint32_t aBaseOffset) +{ + FontRange* fontRange = aFontRanges.AppendElement(); + fontRange->mStartOffset = aBaseOffset; + return fontRange; +} + +/* static */ uint32_t +ContentEventHandler::GetTextLengthInRange(nsIContent* aContent, + uint32_t aXPStartOffset, + uint32_t aXPEndOffset, + LineBreakType aLineBreakType) +{ + return aLineBreakType == LINE_BREAK_TYPE_NATIVE ? + GetNativeTextLength(aContent, aXPStartOffset, aXPEndOffset) : + aXPEndOffset - aXPStartOffset; +} + +/* static */ void +ContentEventHandler::AppendFontRanges(FontRangeArray& aFontRanges, + nsIContent* aContent, + int32_t aBaseOffset, + int32_t aXPStartOffset, + int32_t aXPEndOffset, + LineBreakType aLineBreakType) +{ + nsIFrame* frame = aContent->GetPrimaryFrame(); + if (!frame) { + // It is a non-rendered content, create an empty range for it. + AppendFontRange(aFontRanges, aBaseOffset); + return; + } + + int32_t baseOffset = aBaseOffset; + nsTextFrame* curr = do_QueryFrame(frame); + MOZ_ASSERT(curr, "Not a text frame"); + while (curr) { + int32_t frameXPStart = std::max(curr->GetContentOffset(), aXPStartOffset); + int32_t frameXPEnd = std::min(curr->GetContentEnd(), aXPEndOffset); + if (frameXPStart >= frameXPEnd) { + curr = static_cast(curr->GetNextContinuation()); + continue; + } + + gfxSkipCharsIterator iter = curr->EnsureTextRun(nsTextFrame::eInflated); + gfxTextRun* textRun = curr->GetTextRun(nsTextFrame::eInflated); + + nsTextFrame* next = nullptr; + if (frameXPEnd < aXPEndOffset) { + next = static_cast(curr->GetNextContinuation()); + while (next && next->GetTextRun(nsTextFrame::eInflated) == textRun) { + frameXPEnd = std::min(next->GetContentEnd(), aXPEndOffset); + next = frameXPEnd < aXPEndOffset ? + static_cast(next->GetNextContinuation()) : nullptr; + } + } + + uint32_t skipStart = iter.ConvertOriginalToSkipped(frameXPStart); + uint32_t skipEnd = iter.ConvertOriginalToSkipped(frameXPEnd); + gfxTextRun::GlyphRunIterator runIter( + textRun, skipStart, skipEnd - skipStart); + int32_t lastXPEndOffset = frameXPStart; + while (runIter.NextRun()) { + gfxFont* font = runIter.GetGlyphRun()->mFont.get(); + int32_t startXPOffset = + iter.ConvertSkippedToOriginal(runIter.GetStringStart()); + // It is possible that the first glyph run has exceeded the frame, + // because the whole frame is filled by skipped chars. + if (startXPOffset >= frameXPEnd) { + break; + } + + if (startXPOffset > lastXPEndOffset) { + // Create range for skipped leading chars. + AppendFontRange(aFontRanges, baseOffset); + baseOffset += GetTextLengthInRange( + aContent, lastXPEndOffset, startXPOffset, aLineBreakType); + lastXPEndOffset = startXPOffset; + } + + FontRange* fontRange = AppendFontRange(aFontRanges, baseOffset); + fontRange->mFontName = font->GetName(); + fontRange->mFontSize = font->GetAdjustedSize(); + + // The converted original offset may exceed the range, + // hence we need to clamp it. + int32_t endXPOffset = + iter.ConvertSkippedToOriginal(runIter.GetStringEnd()); + endXPOffset = std::min(frameXPEnd, endXPOffset); + baseOffset += GetTextLengthInRange(aContent, startXPOffset, endXPOffset, + aLineBreakType); + lastXPEndOffset = endXPOffset; + } + if (lastXPEndOffset < frameXPEnd) { + // Create range for skipped trailing chars. It also handles case + // that the whole frame contains only skipped chars. + AppendFontRange(aFontRanges, baseOffset); + baseOffset += GetTextLengthInRange( + aContent, lastXPEndOffset, frameXPEnd, aLineBreakType); + } + + curr = next; + } +} + +/* static */ nsresult +ContentEventHandler::GenerateFlatFontRanges(nsRange* aRange, + FontRangeArray& aFontRanges, + uint32_t& aLength, + LineBreakType aLineBreakType) +{ + MOZ_ASSERT(aFontRanges.IsEmpty(), "aRanges must be empty array"); + + nsINode* startNode = aRange->GetStartParent(); + nsINode* endNode = aRange->GetEndParent(); + if (NS_WARN_IF(!startNode || !endNode)) { + return NS_ERROR_FAILURE; + } + + // baseOffset is the flattened offset of each content node. + int32_t baseOffset = 0; + nsCOMPtr iter = NS_NewContentIterator(); + for (iter->Init(aRange); !iter->IsDone(); iter->Next()) { + nsINode* node = iter->GetCurrentNode(); + if (NS_WARN_IF(!node)) { + break; + } + if (!node->IsContent()) { + continue; + } + nsIContent* content = node->AsContent(); + + if (content->IsNodeOfType(nsINode::eTEXT)) { + int32_t startOffset = content != startNode ? 0 : aRange->StartOffset(); + int32_t endOffset = content != endNode ? + content->TextLength() : aRange->EndOffset(); + AppendFontRanges(aFontRanges, content, baseOffset, + startOffset, endOffset, aLineBreakType); + baseOffset += GetTextLengthInRange(content, startOffset, endOffset, + aLineBreakType); + } else if (IsContentBR(content)) { + if (aFontRanges.IsEmpty()) { + MOZ_ASSERT(baseOffset == 0); + FontRange* fontRange = AppendFontRange(aFontRanges, baseOffset); + nsIFrame* frame = content->GetPrimaryFrame(); + if (frame) { + const nsFont& font = frame->GetParent()->StyleFont()->mFont; + const FontFamilyList& fontList = font.fontlist; + const FontFamilyName& fontName = fontList.IsEmpty() ? + FontFamilyName(fontList.GetDefaultFontType()) : + fontList.GetFontlist()[0]; + fontName.AppendToString(fontRange->mFontName, false); + fontRange->mFontSize = + frame->PresContext()->AppUnitsToDevPixels(font.size); + } + } + baseOffset += GetBRLength(aLineBreakType); + } + } + + aLength = baseOffset; + return NS_OK; +} + nsresult ContentEventHandler::ExpandToClusterBoundary(nsIContent* aContent, bool aForward, @@ -697,6 +867,18 @@ ContentEventHandler::OnQueryTextContent(WidgetQueryContentEvent* aEvent) rv = GenerateFlatTextContent(range, aEvent->mReply.mString, lineBreakType); NS_ENSURE_SUCCESS(rv, rv); + if (aEvent->mWithFontRanges) { + uint32_t fontRangeLength; + rv = GenerateFlatFontRanges(range, aEvent->mReply.mFontRanges, + fontRangeLength, lineBreakType); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + MOZ_ASSERT(fontRangeLength == aEvent->mReply.mString.Length(), + "Font ranges doesn't match the string"); + } + aEvent->mSucceeded = true; return NS_OK; diff --git a/dom/events/ContentEventHandler.h b/dom/events/ContentEventHandler.h index a86e56eee71..f1c338b5cc3 100644 --- a/dom/events/ContentEventHandler.h +++ b/dom/events/ContentEventHandler.h @@ -94,6 +94,12 @@ public: // Get the native text length of a content node excluding any children static uint32_t GetNativeTextLength(nsIContent* aContent, uint32_t aMaxLength = UINT32_MAX); + // Get the text length of a given range of a content node in + // the given line break type. + static uint32_t GetTextLengthInRange(nsIContent* aContent, + uint32_t aXPStartOffset, + uint32_t aXPEndOffset, + LineBreakType aLineBreakType); protected: static uint32_t GetTextLength(nsIContent* aContent, LineBreakType aLineBreakType, @@ -129,6 +135,18 @@ protected: // true, it is expanded to forward. nsresult ExpandToClusterBoundary(nsIContent* aContent, bool aForward, uint32_t* aXPOffset); + + typedef nsTArray FontRangeArray; + static void AppendFontRanges(FontRangeArray& aFontRanges, + nsIContent* aContent, + int32_t aBaseOffset, + int32_t aXPStartOffset, + int32_t aXPEndOffset, + LineBreakType aLineBreakType); + static nsresult GenerateFlatFontRanges(nsRange* aRange, + FontRangeArray& aFontRanges, + uint32_t& aLength, + LineBreakType aLineBreakType); }; } // namespace mozilla diff --git a/ipc/glue/IPCMessageUtils.h b/ipc/glue/IPCMessageUtils.h index a1d0d7629a9..673d905c4de 100644 --- a/ipc/glue/IPCMessageUtils.h +++ b/ipc/glue/IPCMessageUtils.h @@ -544,6 +544,12 @@ struct ParamTraits > } }; +template +struct ParamTraits> : ParamTraits> +{ + typedef nsAutoTArray paramType; +}; + template<> struct ParamTraits { diff --git a/widget/EventForwards.h b/widget/EventForwards.h index 72a768c53af..25567b55b4f 100644 --- a/widget/EventForwards.h +++ b/widget/EventForwards.h @@ -112,6 +112,9 @@ struct TextRange; class TextRangeArray; +// FontRange.h +struct FontRange; + } // namespace mozilla #endif // mozilla_EventForwards_h__ diff --git a/widget/FontRange.h b/widget/FontRange.h new file mode 100644 index 00000000000..e2de0f4a142 --- /dev/null +++ b/widget/FontRange.h @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_FontRange_h_ +#define mozilla_FontRange_h_ + +namespace mozilla { + +struct FontRange +{ + FontRange() + : mStartOffset(0) + , mFontSize(0) + { + } + + int32_t mStartOffset; + nsString mFontName; + gfxFloat mFontSize; // in device pixels +}; + +} + +#endif // mozilla_FontRange_h_ diff --git a/widget/TextEvents.h b/widget/TextEvents.h index 957a7b15ac5..1367b5c38ce 100644 --- a/widget/TextEvents.h +++ b/widget/TextEvents.h @@ -12,6 +12,7 @@ #include "mozilla/BasicEvents.h" #include "mozilla/EventForwards.h" // for KeyNameIndex, temporarily #include "mozilla/TextRange.h" +#include "mozilla/FontRange.h" #include "nsCOMPtr.h" #include "nsIDOMKeyEvent.h" #include "nsITransferable.h" @@ -399,6 +400,7 @@ public: , mSucceeded(false) , mWasAsync(false) , mUseNativeLineBreak(true) + , mWithFontRanges(false) { } @@ -447,6 +449,13 @@ public: refPoint = aPoint; } + void RequestFontRanges() + { + NS_ASSERTION(message == NS_QUERY_TEXT_CONTENT, + "not querying text content"); + mWithFontRanges = true; + } + uint32_t GetSelectionStart(void) const { NS_ASSERTION(message == NS_QUERY_SELECTED_TEXT, @@ -471,6 +480,7 @@ public: bool mSucceeded; bool mWasAsync; bool mUseNativeLineBreak; + bool mWithFontRanges; struct { uint32_t mOffset; @@ -495,6 +505,8 @@ public: mozilla::WritingMode mWritingMode; // used by NS_QUERY_SELECTION_AS_TRANSFERABLE nsCOMPtr mTransferable; + // used by NS_QUERY_TEXT_CONTENT with font ranges requested + nsAutoTArray mFontRanges; } mReply; enum diff --git a/widget/moz.build b/widget/moz.build index 422db4cc47e..71c9beacc85 100644 --- a/widget/moz.build +++ b/widget/moz.build @@ -117,6 +117,7 @@ EXPORTS.mozilla += [ 'ContentEvents.h', 'EventClassList.h', 'EventForwards.h', + 'FontRange.h', 'LookAndFeel.h', 'MiscEvents.h', 'MouseEvents.h', diff --git a/widget/nsGUIEventIPC.h b/widget/nsGUIEventIPC.h index 82b9ceec5fd..bd17ccaf049 100644 --- a/widget/nsGUIEventIPC.h +++ b/widget/nsGUIEventIPC.h @@ -525,6 +525,26 @@ struct ParamTraits } }; +template<> +struct ParamTraits +{ + typedef mozilla::FontRange paramType; + + static void Write(Message* aMsg, const paramType& aParam) + { + WriteParam(aMsg, aParam.mStartOffset); + WriteParam(aMsg, aParam.mFontName); + WriteParam(aMsg, aParam.mFontSize); + } + + static bool Read(const Message* aMsg, void** aIter, paramType* aResult) + { + return ReadParam(aMsg, aIter, &aResult->mStartOffset) && + ReadParam(aMsg, aIter, &aResult->mFontName) && + ReadParam(aMsg, aIter, &aResult->mFontSize); + } +}; + template<> struct ParamTraits { @@ -535,6 +555,7 @@ struct ParamTraits WriteParam(aMsg, static_cast(aParam)); WriteParam(aMsg, aParam.mSucceeded); WriteParam(aMsg, aParam.mUseNativeLineBreak); + WriteParam(aMsg, aParam.mWithFontRanges); WriteParam(aMsg, aParam.mInput.mOffset); WriteParam(aMsg, aParam.mInput.mLength); WriteParam(aMsg, aParam.mReply.mOffset); @@ -543,6 +564,7 @@ struct ParamTraits WriteParam(aMsg, aParam.mReply.mReversed); WriteParam(aMsg, aParam.mReply.mHasSelection); WriteParam(aMsg, aParam.mReply.mWidgetIsHit); + WriteParam(aMsg, aParam.mReply.mFontRanges); } static bool Read(const Message* aMsg, void** aIter, paramType* aResult) @@ -552,6 +574,7 @@ struct ParamTraits static_cast(aResult)) && ReadParam(aMsg, aIter, &aResult->mSucceeded) && ReadParam(aMsg, aIter, &aResult->mUseNativeLineBreak) && + ReadParam(aMsg, aIter, &aResult->mWithFontRanges) && ReadParam(aMsg, aIter, &aResult->mInput.mOffset) && ReadParam(aMsg, aIter, &aResult->mInput.mLength) && ReadParam(aMsg, aIter, &aResult->mReply.mOffset) && @@ -559,7 +582,8 @@ struct ParamTraits ReadParam(aMsg, aIter, &aResult->mReply.mRect) && ReadParam(aMsg, aIter, &aResult->mReply.mReversed) && ReadParam(aMsg, aIter, &aResult->mReply.mHasSelection) && - ReadParam(aMsg, aIter, &aResult->mReply.mWidgetIsHit); + ReadParam(aMsg, aIter, &aResult->mReply.mWidgetIsHit) && + ReadParam(aMsg, aIter, &aResult->mReply.mFontRanges); } }; From eea4d8ca38d1d3a5873264533dbae83bc4a697a2 Mon Sep 17 00:00:00 2001 From: Xidorn Quan Date: Sat, 31 Jan 2015 18:17:18 +1100 Subject: [PATCH 098/101] Bug 569334 part 2 - Provide font info for content query on Mac. r=smichaud --- widget/cocoa/TextInputHandler.mm | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/widget/cocoa/TextInputHandler.mm b/widget/cocoa/TextInputHandler.mm index 93d0191a896..41da67b6e49 100644 --- a/widget/cocoa/TextInputHandler.mm +++ b/widget/cocoa/TextInputHandler.mm @@ -2975,6 +2975,7 @@ IMEInputHandler::GetAttributedSubstringFromRange(NSRange& aRange, nsAutoString str; WidgetQueryContentEvent textContent(true, NS_QUERY_TEXT_CONTENT, mWidget); textContent.InitForQueryTextContent(aRange.location, aRange.length); + textContent.RequestFontRanges(); DispatchEvent(textContent); PR_LOG(gLog, PR_LOG_ALWAYS, @@ -2989,9 +2990,26 @@ IMEInputHandler::GetAttributedSubstringFromRange(NSRange& aRange, } NSString* nsstr = nsCocoaUtils::ToNSString(textContent.mReply.mString); - NSAttributedString* result = - [[[NSAttributedString alloc] initWithString:nsstr - attributes:nil] autorelease]; + NSMutableAttributedString* result = + [[[NSMutableAttributedString alloc] initWithString:nsstr + attributes:nil] autorelease]; + const nsTArray& fontRanges = textContent.mReply.mFontRanges; + int32_t lastOffset = textContent.mReply.mString.Length(); + for (auto i = fontRanges.Length(); i > 0; --i) { + const FontRange& fontRange = fontRanges[i - 1]; + NSString* fontName = nsCocoaUtils::ToNSString(fontRange.mFontName); + CGFloat fontSize = fontRange.mFontSize / mWidget->BackingScaleFactor(); + NSFont* font = [NSFont fontWithName:fontName size:fontSize]; + if (!font) { + font = [NSFont systemFontOfSize:fontSize]; + } + + NSDictionary* attrs = @{ NSFontAttributeName: font }; + NSRange range = NSMakeRange(fontRange.mStartOffset, + lastOffset - fontRange.mStartOffset); + [result setAttributes:attrs range:range]; + lastOffset = fontRange.mStartOffset; + } if (aActualRange) { aActualRange->location = textContent.mReply.mOffset; aActualRange->length = textContent.mReply.mString.Length(); From f5b851b52b5f4b70d481a0e016cf4582a2219323 Mon Sep 17 00:00:00 2001 From: ffxbld Date: Sat, 31 Jan 2015 03:38:07 -0800 Subject: [PATCH 099/101] No bug, Automated HSTS preload list update from host bld-linux64-spot-015 - a=hsts-update --- .../manager/boot/src/nsSTSPreloadList.errors | 59 +++++++------- .../manager/boot/src/nsSTSPreloadList.inc | 76 ++++++++++++++++++- 2 files changed, 105 insertions(+), 30 deletions(-) diff --git a/security/manager/boot/src/nsSTSPreloadList.errors b/security/manager/boot/src/nsSTSPreloadList.errors index 749eb8fdd8b..23676529024 100644 --- a/security/manager/boot/src/nsSTSPreloadList.errors +++ b/security/manager/boot/src/nsSTSPreloadList.errors @@ -2,6 +2,7 @@ admin.google.com: did not receive HSTS header (error ignored - included regardle adsfund.org: could not connect to host afp548.com: did not receive HSTS header airbnb.com: did not receive HSTS header +airlea.com: could not connect to host aiticon.de: did not receive HSTS header amigogeek.net: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no] api.mega.co.nz: could not connect to host @@ -9,7 +10,7 @@ api.recurly.com: did not receive HSTS header apis.google.com: did not receive HSTS header (error ignored - included regardless) app.manilla.com: could not connect to host appengine.google.com: did not receive HSTS header (error ignored - included regardless) -apps.facebook.com: did not receive HSTS header +apps.facebook.com: max-age too low: 86400 appseccalifornia.org: did not receive HSTS header at.search.yahoo.com: did not receive HSTS header atavio.at: could not connect to host @@ -17,6 +18,7 @@ atavio.ch: could not connect to host au.search.yahoo.com: did not receive HSTS header az.search.yahoo.com: did not receive HSTS header azprep.us: could not connect to host +barcodeberlin.com: did not receive HSTS header bccx.com: could not connect to host be.search.yahoo.com: did not receive HSTS header bedeta.de: could not connect to host @@ -34,7 +36,7 @@ braintreegateway.com: did not receive HSTS header braintreepayments.com: did not receive HSTS header brainvation.de: did not receive HSTS header browserid.org: did not receive HSTS header -business.facebook.com: did not receive HSTS header +business.facebook.com: max-age too low: 86400 business.medbank.com.mt: did not receive HSTS header ca.search.yahoo.com: did not receive HSTS header calibreapp.com: did not receive HSTS header @@ -56,7 +58,7 @@ cl.search.yahoo.com: did not receive HSTS header cloud.google.com: did not receive HSTS header (error ignored - included regardless) cn.search.yahoo.com: did not receive HSTS header co.search.yahoo.com: did not receive HSTS header -code.facebook.com: did not receive HSTS header +code.facebook.com: max-age too low: 86400 code.google.com: did not receive HSTS header (error ignored - included regardless) codereview.chromium.org: did not receive HSTS header (error ignored - included regardless) console.python.org: did not receive HSTS header @@ -76,7 +78,7 @@ daylightcompany.com: did not receive HSTS header de.search.yahoo.com: did not receive HSTS header decibelios.li: did not receive HSTS header destinationbijoux.fr: max-age too low: 2678400 -developers.facebook.com: did not receive HSTS header +developers.facebook.com: max-age too low: 86400 digitaldaddy.net: could not connect to host discovery.lookout.com: did not receive HSTS header dk.search.yahoo.com: did not receive HSTS header @@ -101,11 +103,13 @@ esec.rs: did not receive HSTS header espanol.search.yahoo.com: did not receive HSTS header espra.com: could not connect to host etsysecure.com: could not connect to host -facebook.com: did not receive HSTS header +explodie.org: could not connect to host +facebook.com: max-age too low: 86400 fatzebra.com.au: did not receive HSTS header fi.search.yahoo.com: did not receive HSTS header -fixingdns.com: did not receive HSTS header +fixingdns.com: could not connect to host fj.search.yahoo.com: did not receive HSTS header +fm83.nl: could not connect to host fonetiq.io: could not connect to host fr.search.yahoo.com: did not receive HSTS header friendlink.jp: did not receive HSTS header @@ -127,6 +131,7 @@ gr.search.yahoo.com: did not receive HSTS header grandmascookieblog.com: did not receive HSTS header greplin.com: could not connect to host groups.google.com: did not receive HSTS header (error ignored - included regardless) +gunnarhafdal.com: could not connect to host hachre.de: did not receive HSTS header hackerone-user-content.com: could not connect to host haste.ch: could not connect to host @@ -151,15 +156,15 @@ iop.intuit.com: max-age too low: 86400 irccloud.com: did not receive HSTS header it.search.yahoo.com: did not receive HSTS header itriskltd.com: did not receive HSTS header +jonas-keidel.de: could not connect to host jottit.com: could not connect to host -keeleysam.com: did not receive HSTS header keymaster.lookout.com: did not receive HSTS header kirkforcongress.com: did not receive HSTS header -kirkforsenate.com: did not receive HSTS header -kitsta.com: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no] +kitsta.com: could not connect to host kiwiirc.com: max-age too low: 5256000 klaxn.com: could not connect to host klaxn.org: could not connect to host +koop-bremen.de: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no] kr.search.yahoo.com: did not receive HSTS header kryptera.se: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no] kz.search.yahoo.com: did not receive HSTS header @@ -177,18 +182,17 @@ lu.search.yahoo.com: did not receive HSTS header lumi.do: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no] luxus-russen.de: could not connect to host lv.search.yahoo.com: did not receive HSTS header -m.facebook.com: did not receive HSTS header +m.facebook.com: max-age too low: 86400 m.gparent.org: could not connect to host +magneticanvil.com: could not connect to host mail.google.com: did not receive HSTS header (error ignored - included regardless) -makeyourlaws.org: did not receive HSTS header maktoob.search.yahoo.com: did not receive HSTS header malaysia.search.yahoo.com: did not receive HSTS header man3s.jp: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no] manage.zenpayroll.com: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no] market.android.com: did not receive HSTS header (error ignored - included regardless) -markusueberallassetmanagement.de: could not connect to host marshut.net: could not connect to host -mbasic.facebook.com: did not receive HSTS header +mbasic.facebook.com: max-age too low: 86400 megashur.se: did not receive HSTS header megaxchange.com: did not receive HSTS header minikneet.nl: did not receive HSTS header @@ -198,7 +202,7 @@ mobilethreatnetwork.net: could not connect to host mocloud.eu: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no] movelaria.com.br: did not receive HSTS header mt.search.yahoo.com: did not receive HSTS header -mtouch.facebook.com: did not receive HSTS header +mtouch.facebook.com: max-age too low: 86400 mu.search.yahoo.com: did not receive HSTS header mudcrab.us: could not connect to host mw.search.yahoo.com: did not receive HSTS header @@ -214,6 +218,7 @@ nexth.de: could not connect to host nexth.net: could not connect to host nexth.us: could not connect to host ni.search.yahoo.com: did not receive HSTS header +niloxy.com: could not connect to host nl.search.yahoo.com: did not receive HSTS header no.search.yahoo.com: did not receive HSTS header noexpect.org: could not connect to host @@ -222,17 +227,16 @@ npw.net: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FA nutsandboltsmedia.com: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no] nz.search.yahoo.com: did not receive HSTS header openshift.redhat.com: did not receive HSTS header -otakurepublic.com: could not connect to host +otakurepublic.com: did not receive HSTS header ottospora.nl: could not connect to host pa.search.yahoo.com: did not receive HSTS header passwordbox.com: did not receive HSTS header passwords.google.com: did not receive HSTS header (error ignored - included regardless) -payroll.xero.com: max-age too low: 3600 pe.search.yahoo.com: did not receive HSTS header ph.search.yahoo.com: did not receive HSTS header piratenlogin.de: could not connect to host pisidia.de: did not receive HSTS header -pixel.facebook.com: did not receive HSTS header +pixel.facebook.com: max-age too low: 86400 pk.search.yahoo.com: did not receive HSTS header pl.search.yahoo.com: did not receive HSTS header platform.lookout.com: could not connect to host @@ -241,15 +245,16 @@ pr.search.yahoo.com: did not receive HSTS header pressfreedomfoundation.org: did not receive HSTS header prodpad.com: did not receive HSTS header profiles.google.com: did not receive HSTS header (error ignored - included regardless) -projektzentrisch.de: could not connect to host promecon-gmbh.de: did not receive HSTS header +proximato.com: could not connect to host py.search.yahoo.com: did not receive HSTS header qc.search.yahoo.com: did not receive HSTS header +ragingserenity.com: could not connect to host rapidresearch.me: could not connect to host redlatam.org: did not receive HSTS header redports.org: could not connect to host regar42.fr: could not connect to host -research.facebook.com: did not receive HSTS header +research.facebook.com: max-age too low: 86400 riftnetwork.net: did not receive HSTS header riseup.net: did not receive HSTS header rme.li: did not receive HSTS header @@ -258,14 +263,12 @@ roddis.net: did not receive HSTS header ru.search.yahoo.com: did not receive HSTS header rw.search.yahoo.com: did not receive HSTS header sah3.net: could not connect to host -samuelkeeley.com: did not receive HSTS header saturngames.co.uk: could not connect to host script.google.com: did not receive HSTS header (error ignored - included regardless) se.search.yahoo.com: did not receive HSTS header search.yahoo.com: did not receive HSTS header -secure.facebook.com: did not receive HSTS header +secure.facebook.com: max-age too low: 86400 security.google.com: did not receive HSTS header (error ignored - included regardless) -segu-info.com.ar: max-age too low: 60 semenkovich.com: did not receive HSTS header seomobo.com: did not receive HSTS header seowarp.net: max-age too low: 1576800 @@ -306,8 +309,8 @@ temehu.com: did not receive HSTS header terrax.berlin: could not connect to host th.search.yahoo.com: did not receive HSTS header thomasgriffin.io: did not receive HSTS header -tonywebster.com: could not connect to host -touch.facebook.com: did not receive HSTS header +tor2web.org: could not connect to host +touch.facebook.com: max-age too low: 86400 tr.search.yahoo.com: did not receive HSTS header translate.googleapis.com: did not receive HSTS header (error ignored - included regardless) translatoruk.co.uk: did not receive HSTS header @@ -316,15 +319,17 @@ tv.search.yahoo.com: could not connect to host tw.search.yahoo.com: did not receive HSTS header ua.search.yahoo.com: did not receive HSTS header uk.search.yahoo.com: did not receive HSTS header -upload.facebook.com: did not receive HSTS header +upload.facebook.com: max-age too low: 86400 uprotect.it: could not connect to host +ustr.gov: max-age too low: 86400 uy.search.yahoo.com: did not receive HSTS header uz.search.yahoo.com: did not receive HSTS header ve.search.yahoo.com: did not receive HSTS header -viennan.net: could not connect to host +viennan.net: did not receive HSTS header vn.search.yahoo.com: did not receive HSTS header wallet.google.com: did not receive HSTS header (error ignored - included regardless) webmail.mayfirst.org: did not receive HSTS header +wevahoo.com: did not receive HSTS header whonix.org: did not receive HSTS header wikidsystems.com: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]" nsresult: "0x80004005 (NS_ERROR_FAILURE)" location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134" data: no] wiz.biz: could not connect to host @@ -334,7 +339,7 @@ www.calyxinstitute.org: [Exception... "Component returned failure code: 0x800040 www.cueup.com: could not connect to host www.developer.mydigipass.com: could not connect to host www.elanex.biz: did not receive HSTS header -www.facebook.com: did not receive HSTS header +www.facebook.com: max-age too low: 86400 www.gmail.com: did not receive HSTS header (error ignored - included regardless) www.googlemail.com: did not receive HSTS header (error ignored - included regardless) www.greplin.com: could not connect to host diff --git a/security/manager/boot/src/nsSTSPreloadList.inc b/security/manager/boot/src/nsSTSPreloadList.inc index c54c07561bb..30ca23e8ee3 100644 --- a/security/manager/boot/src/nsSTSPreloadList.inc +++ b/security/manager/boot/src/nsSTSPreloadList.inc @@ -8,7 +8,7 @@ /*****************************************************************************/ #include -const PRTime gPreloadListExpirationTime = INT64_C(1432984708039000); +const PRTime gPreloadListExpirationTime = INT64_C(1433590160369000); class nsSTSPreload { @@ -39,6 +39,7 @@ static const nsSTSPreload kSTSPreloadList[] = { { "adsfund.org", true }, { "ahoyconference.com", true }, { "ahwatukeefoothillsmontessori.com", true }, + { "aids.gov", true }, { "aie.de", true }, { "aiticon.com", true }, { "aladdinschools.appspot.com", false }, @@ -82,6 +83,7 @@ static const nsSTSPreload kSTSPreloadList[] = { { "atavio.at", true }, { "atavio.ch", true }, { "atavio.de", true }, + { "atlantischild.hu", true }, { "atlassian.net", true }, { "atte.fi", true }, { "auf-feindgebiet.de", true }, @@ -92,7 +94,6 @@ static const nsSTSPreload kSTSPreloadList[] = { { "baldwinkoo.com", true }, { "balikonos.cz", true }, { "bank.simple.com", false }, - { "barcodeberlin.com", true }, { "barslecht.com", true }, { "barslecht.nl", true }, { "baruch.me", true }, @@ -107,8 +108,10 @@ static const nsSTSPreload kSTSPreloadList[] = { { "bedeta.de", true }, { "bedreid.dk", true }, { "beneathvt.com", true }, + { "benjamin.pe", true }, { "benjamins.com", true }, { "best-wedding-quotes.com", true }, + { "bfelob.gov", true }, { "bgneuesheim.de", true }, { "bhatia.at", true }, { "biathloncup.ru", true }, @@ -158,6 +161,7 @@ static const nsSTSPreload kSTSPreloadList[] = { { "call.me", true }, { "calomel.org", true }, { "camolist.com", true }, + { "cao.gov", true }, { "caremad.io", true }, { "carezone.com", false }, { "cartouche24.eu", true }, @@ -166,8 +170,10 @@ static const nsSTSPreload kSTSPreloadList[] = { { "celltek-server.de", false }, { "certible.com", true }, { "certly.io", true }, + { "cfo.gov", true }, { "chahub.com", true }, { "chainmonitor.com", true }, + { "changelab.cc", true }, { "chatbot.me", true }, { "check.torproject.org", false }, { "checkout.google.com", true }, @@ -178,6 +184,7 @@ static const nsSTSPreload kSTSPreloadList[] = { { "chrome.google.com", true }, { "chromiumcodereview.appspot.com", false }, { "chulado.com", true }, + { "cio.gov", true }, { "cktennis.com", true }, { "clan-ww.com", true }, { "clapping-rhymes.com", true }, @@ -208,9 +215,11 @@ static const nsSTSPreload kSTSPreloadList[] = { { "crm.onlime.ch", false }, { "crowdjuris.com", true }, { "crypto.cat", false }, + { "cryptobin.org", true }, { "cryptopartyatx.org", true }, { "cs50.harvard.edu", true }, { "cspbuilder.info", true }, + { "csuw.net", true }, { "cube.de", true }, { "cupcake.io", true }, { "cupcake.is", true }, @@ -229,6 +238,7 @@ static const nsSTSPreload kSTSPreloadList[] = { { "datenkeks.de", true }, { "daveoc64.co.uk", true }, { "davidlyness.com", true }, + { "dccode.gov", true }, { "deadbeef.ninja", true }, { "dealcruiser.nl", true }, { "debtkit.co.uk", true }, @@ -256,12 +266,17 @@ static const nsSTSPreload kSTSPreloadList[] = { { "docs.python.org", true }, { "domains.google.com", true }, { "donmez.ws", false }, + { "dreadbyte.com", true }, { "drive.google.com", true }, { "dropbox.com", true }, + { "dylanscott.com.au", true }, { "dzlibs.io", true }, + { "e-kontakti.fi", true }, + { "earmarks.gov", true }, { "easysimplecrm.com", false }, { "ebanking.indovinabank.com.vn", false }, { "ecdn.cz", true }, + { "ecfs.link", true }, { "ecosystem.atlassian.net", true }, { "edit.yahoo.com", false }, { "eduroam.no", true }, @@ -288,6 +303,7 @@ static const nsSTSPreload kSTSPreloadList[] = { { "everhome.de", true }, { "evstatus.com", true }, { "exiahost.com", true }, + { "expatads.com", true }, { "explodie.org", true }, { "f-droid.org", true }, { "fabhub.io", true }, @@ -317,6 +333,7 @@ static const nsSTSPreload kSTSPreloadList[] = { { "flamer-scene.com", true }, { "fleximus.org", false }, { "floobits.com", true }, + { "florianmitrea.uk", true }, { "flynn.io", true }, { "fm83.nl", true }, { "food4health.guide", true }, @@ -336,10 +353,12 @@ static const nsSTSPreload kSTSPreloadList[] = { { "gamercredo.com", true }, { "garron.net", true }, { "gavick.com", true }, + { "gaytorrent.ru", true }, { "gemeinfreie-lieder.de", true }, { "gerardozamudio.mx", true }, { "gernert-server.de", true }, { "get.zenpayroll.com", false }, + { "getable.com", true }, { "getcloak.com", false }, { "getdigitized.net", true }, { "getssl.uz", true }, @@ -358,12 +377,14 @@ static const nsSTSPreload kSTSPreloadList[] = { { "googleplex.com", true }, { "goto.google.com", true }, { "gplintegratedit.com", true }, + { "gpsfix.cz", true }, { "grc.com", false }, { "greensolid.biz", true }, { "grepular.com", true }, { "groetzner.net", true }, { "groups.google.com", true }, { "gtraxapp.com", true }, + { "guidetoiceland.is", true }, { "gunnarhafdal.com", true }, { "guphi.net", true }, { "guthabenkarten-billiger.de", true }, @@ -371,6 +392,7 @@ static const nsSTSPreload kSTSPreloadList[] = { { "hack.li", true }, { "hackerone.com", true }, { "hansvaneijsden.com", true }, + { "happylifestyle.com", true }, { "harvestapp.com", true }, { "hasilocke.de", true }, { "haste.ch", true }, @@ -385,6 +407,7 @@ static const nsSTSPreload kSTSPreloadList[] = { { "helpium.de", true }, { "hemlockhillscabinrentals.com", true }, { "henriknoerr.com", true }, + { "heppler.net", true }, { "herocentral.de", true }, { "hex2013.com", true }, { "hexony.com", true }, @@ -405,6 +428,7 @@ static const nsSTSPreload kSTSPreloadList[] = { { "hsmr.cc", true }, { "hstsfail.appspot.com", true }, { "html5.org", true }, + { "httpswatch.com", true }, { "i5y.co.uk", true }, { "iamcarrico.com", true }, { "ian.sh", true }, @@ -428,14 +452,17 @@ static const nsSTSPreload kSTSPreloadList[] = { { "innophate-security.nl", true }, { "insouciant.org", true }, { "instasex.ch", true }, + { "interserved.com", true }, { "iranianlawschool.com", true }, { "iridiumbrowser.de", true }, { "irische-segenswuensche.info", true }, { "ironfistdesign.com", true }, { "isitchristmas.com", true }, { "it-schwerin.de", true }, + { "itdashboard.gov", true }, { "itsamurai.ru", true }, { "itshost.ru", true }, + { "izdiwho.com", true }, { "jackyyf.com", false }, { "jakub-boucek.cz", true }, { "janoberst.com", true }, @@ -444,12 +471,15 @@ static const nsSTSPreload kSTSPreloadList[] = { { "jelmer.uk", true }, { "jettshome.org", true }, { "jfreitag.de", true }, + { "jimshaver.net", true }, { "jira.com", true }, { "jitsi.org", false }, + { "jmdekker.it", true }, { "jmedved.com", true }, { "johners.me", true }, { "jonas-keidel.de", true }, { "jonaswitmer.ch", true }, + { "jonathan.ir", true }, { "jonnybarnes.uk", true }, { "julian-kipka.de", true }, { "jwilsson.com", true }, @@ -461,6 +491,7 @@ static const nsSTSPreload kSTSPreloadList[] = { { "kartonmodellbau.org", true }, { "kdex.de", true }, { "kdyby.org", true }, + { "keeleysam.com", true }, { "keepclean.me", true }, { "keeperapp.com", true }, { "keepersecurity.com", true }, @@ -476,11 +507,14 @@ static const nsSTSPreload kSTSPreloadList[] = { { "kinogb.net", false }, { "kinsights.com", false }, { "kirei.se", true }, + { "kirkforsenate.com", true }, { "kitsta.com", true }, + { "klarmobil-empfehlen.de", true }, { "klatschreime.de", true }, { "klausbrinch.dk", true }, { "klaxn.com", true }, { "kleidertauschpartys.de", true }, + { "klingeletest.de", true }, { "knowledgehook.com", true }, { "koenvdheuvel.me", true }, { "komandakovalchuk.com", true }, @@ -494,6 +528,7 @@ static const nsSTSPreload kSTSPreloadList[] = { { "kupschke.net", true }, { "kura.io", true }, { "lagerauftrag.info", true }, + { "lancejames.com", true }, { "lasst-uns-beten.de", true }, { "lastpass.com", false }, { "launchkey.com", true }, @@ -502,6 +537,7 @@ static const nsSTSPreload kSTSPreloadList[] = { { "leadbook.ru", true }, { "leibniz-remscheid.de", true }, { "leonardcamacho.me", true }, + { "leonklingele.de", true }, { "les-corsaires.net", true }, { "libraryfreedomproject.org", true }, { "liebel.org", true }, @@ -534,12 +570,14 @@ static const nsSTSPreload kSTSPreloadList[] = { { "lumi.do", false }, { "luneta.nearbuysystems.com", false }, { "mach-politik.ch", true }, + { "madars.org", true }, { "maff.scot", false }, { "mail.de", true }, { "mail.google.com", true }, { "mail.yahoo.com", false }, { "mailbox.org", true }, { "makeitdynamic.com", true }, + { "makeyourlaws.org", true }, { "malnex.de", true }, { "man3s.jp", true }, { "manage.zenpayroll.com", false }, @@ -554,6 +592,7 @@ static const nsSTSPreload kSTSPreloadList[] = { { "mathiasbynens.be", true }, { "matteomarescotti.it", true }, { "mattmccutchen.net", true }, + { "max.gov", true }, { "mbp.banking.co.at", false }, { "md5file.com", true }, { "mdfnet.se", true }, @@ -570,18 +609,22 @@ static const nsSTSPreload kSTSPreloadList[] = { { "mig5.net", true }, { "mike-bland.com", true }, { "mikewest.org", true }, - { "miku.hatsune.my", true }, + { "miku.hatsune.my", false }, + { "mimeit.de", true }, { "minecraftvoter.com", true }, { "minez-nightswatch.com", true }, { "minikneet.com", true }, + { "miniku.net", true }, { "minnesotadata.com", true }, { "mirrorx.com", true }, { "miskatonic.org", true }, { "mkcert.org", true }, { "mkw.st", true }, { "mnsure.org", true }, + { "mobilcom-debitel-empfehlen.de", true }, { "mobile.usaa.com", false }, { "mondwandler.de", true }, + { "morethanadream.lv", true }, { "moriz.de", true }, { "mothereff.in", true }, { "mountainmusicpromotions.com", true }, @@ -612,12 +655,14 @@ static const nsSTSPreload kSTSPreloadList[] = { { "nachsenden.info", true }, { "nameid.org", true }, { "namepros.com", true }, + { "narodniki.com", true }, { "nectarleaf.com", true }, { "neg9.org", false }, { "neilwynne.com", false }, { "neko.li", true }, { "net-safe.info", true }, { "netera.se", true }, + { "netrider.net.au", true }, { "netzbit.de", true }, { "netztest.at", true }, { "newstarnootropics.com", true }, @@ -625,8 +670,11 @@ static const nsSTSPreload kSTSPreloadList[] = { { "nginxnudes.com", true }, { "nieselregen.com", true }, { "nmctest.net", true }, + { "noobs-r-us.co.uk", true }, { "nos-oignons.net", true }, + { "notalone.gov", true }, { "nouvelle-vague-saint-cast.fr", true }, + { "nowhere.dk", true }, { "npw.net", true }, { "nu3.at", true }, { "nu3.ch", true }, @@ -682,17 +730,21 @@ static const nsSTSPreload kSTSPreloadList[] = { { "patt.us", true }, { "pauladamsmith.com", true }, { "pay.gigahost.dk", true }, + { "paymentaccuracy.gov", true }, { "paymill.com", true }, { "paymill.de", true }, { "paypal.com", false }, + { "payroll.xero.com", false }, { "pdf.yt", true }, { "peercraft.com", true }, { "pentesterlab.com", true }, + { "perfectionis.me", true }, { "personaldatabasen.no", true }, { "pestici.de", true }, { "petrolplus.ru", true }, { "pharmaboard.de", true }, { "phoenixlogan.com", true }, + { "phryanjr.com", false }, { "phurl.de", true }, { "picksin.club", true }, { "pieperhome.de", true }, @@ -702,6 +754,7 @@ static const nsSTSPreload kSTSPreloadList[] = { { "plothost.com", true }, { "plus.google.com", false }, { "plus.sandbox.google.com", false }, + { "polymathematician.com", true }, { "portal.tirol.gv.at", true }, { "posteo.de", false }, { "powerplannerapp.com", true }, @@ -764,7 +817,11 @@ static const nsSTSPreload kSTSPreloadList[] = { { "salserocafe.com", true }, { "samba.org", true }, { "samizdat.cz", true }, + { "samuelkeeley.com", true }, + { "sanatfilan.com", false }, { "sandbox.mydigipass.com", false }, + { "save.gov", true }, + { "saveaward.gov", true }, { "savetheinternet.eu", true }, { "savvytime.com", true }, { "schachburg.de", true }, @@ -796,6 +853,7 @@ static const nsSTSPreload kSTSPreloadList[] = { { "shodan.io", true }, { "shopontarget.com", true }, { "shortdiary.me", true }, + { "sikayetvar.com", true }, { "silentcircle.com", false }, { "simbolo.co.uk", false }, { "simple.com", false }, @@ -850,6 +908,7 @@ static const nsSTSPreload kSTSPreloadList[] = { { "strongest-privacy.com", true }, { "studienportal.eu", true }, { "studydrive.net", true }, + { "stulda.cz", true }, { "subrosa.io", true }, { "suite73.org", true }, { "sunjaydhama.com", true }, @@ -860,11 +919,13 @@ static const nsSTSPreload kSTSPreloadList[] = { { "swehack.org", false }, { "sylaps.com", true }, { "sysctl.se", true }, + { "sysdb.io", true }, { "syss.de", true }, { "tadigitalstore.com", true }, { "tageau.com", true }, { "talk.google.com", true }, { "talkgadget.google.com", true }, + { "tallshoe.com", true }, { "tapka.cz", true }, { "tas2580.net", true }, { "tatort-fanpage.de", true }, @@ -881,6 +942,7 @@ static const nsSTSPreload kSTSPreloadList[] = { { "thepaymentscompany.com", true }, { "therapynotes.com", false }, { "theshadestore.com", true }, + { "thetomharling.com", true }, { "thomastimepieces.com.au", true }, { "thorncreek.net", false }, { "thusoy.com", true }, @@ -916,10 +978,12 @@ static const nsSTSPreload kSTSPreloadList[] = { { "tonermonster.de", true }, { "tonex.de", true }, { "tonex.nl", true }, + { "tonywebster.com", true }, { "topodin.com", true }, { "tor2web.org", true }, { "torproject.org", false }, { "toshnix.com", true }, + { "tracktivity.com.au", true }, { "translate.googleapis.com", true }, { "trauertexte.info", true }, { "tresorit.com", true }, @@ -929,6 +993,7 @@ static const nsSTSPreload kSTSPreloadList[] = { { "twentymilliseconds.com", true }, { "twisto.cz", true }, { "twitter.com", false }, + { "twitteroauth.com", true }, { "typingrevolution.com", true }, { "ub3rk1tten.com", true }, { "ubertt.org", true }, @@ -940,10 +1005,12 @@ static const nsSTSPreload kSTSPreloadList[] = { { "uonstaffhub.com", true }, { "uptrends.com", true }, { "usaa.com", false }, + { "uspsoig.gov", true }, { "vaddder.com", true }, { "vhost.co.id", true }, { "viasinc.com", false }, { "visionless.me", false }, + { "vitrado.de", true }, { "vmoagents.com", false }, { "vocaloid.my", true }, { "vortexhobbies.com", true }, @@ -967,6 +1034,7 @@ static const nsSTSPreload kSTSPreloadList[] = { { "webmail.onlime.ch", false }, { "webmail.schokokeks.org", true }, { "websenat.de", true }, + { "webtalis.nl", true }, { "webtiles.co.uk", true }, { "webtrh.cz", true }, { "weggeweest.nl", true }, @@ -1052,8 +1120,10 @@ static const nsSTSPreload kSTSPreloadList[] = { { "ypart.eu", true }, { "z.ai", true }, { "zenpayroll.com", false }, + { "zentralwolke.de", true }, { "zeplin.io", false }, { "zeropush.com", true }, + { "zhovner.com", false }, { "zixiao.wang", true }, { "zlavomat.sk", true }, }; From a691b83b2451923a1e4772d5fa0b78ce2b608884 Mon Sep 17 00:00:00 2001 From: ffxbld Date: Sat, 31 Jan 2015 03:38:09 -0800 Subject: [PATCH 100/101] No bug, Automated HPKP preload list update from host bld-linux64-spot-015 - a=hpkp-update --- security/manager/boot/src/StaticHPKPins.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/manager/boot/src/StaticHPKPins.h b/security/manager/boot/src/StaticHPKPins.h index 932c279eb2a..119192935fc 100644 --- a/security/manager/boot/src/StaticHPKPins.h +++ b/security/manager/boot/src/StaticHPKPins.h @@ -1080,4 +1080,4 @@ static const TransportSecurityPreload kPublicKeyPinningPreloadList[] = { static const int32_t kUnknownId = -1; -static const PRTime kPreloadPKPinsExpirationTime = INT64_C(1430565512682000); +static const PRTime kPreloadPKPinsExpirationTime = INT64_C(1431170970234000); From a2b2b6e3b9c2a2455ae3a31775a3619c623e148f Mon Sep 17 00:00:00 2001 From: ffxbld Date: Sat, 31 Jan 2015 03:38:10 -0800 Subject: [PATCH 101/101] No bug, Automated blocklist update from host bld-linux64-spot-015 - a=blocklist-update --- browser/app/blocklist.xml | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/browser/app/blocklist.xml b/browser/app/blocklist.xml index 46c5842bdb4..e70344cd3cc 100644 --- a/browser/app/blocklist.xml +++ b/browser/app/blocklist.xml @@ -1,5 +1,5 @@ - + @@ -953,6 +953,12 @@ + + + + + + @@ -2792,6 +2798,18 @@ https://get.adobe.com/flashplayer/ + + + https://get.adobe.com/flashplayer/ + + + + https://get.adobe.com/flashplayer/ + + + + https://get.adobe.com/flashplayer/ +