<commit_before>/**
 * Created by zhangjinjie on 2017/5/17.
 */
import store from './store';
import * as types from './store/types';
import consts from './consts';

export default class Auth {
  constructor(Vue, opts = {}) {
    // check Axios, Vue-router, Vuex used
    if (!Vue.$http && !Vue.prototype.$http) {
      console.error('Axios must be set first');
      return;
    }

    if (!Vue.router) {
      console.error('Vue router 2.x must be set first');
      return;
    }

    if (!Vue.store) {
      console.error('Vuex must be used first');
      return;
    }

    // app key
    if (!opts.appKey) {
      console.error('must set app key first');
      return;
    }

    // set variables;
    this._Vue = Vue;

    // options
    const defaultOptions = {
      absPath: true,
      appKey: '',
      authType: 'Bearer',
      hamletPrefix: '/api/auth',
      authRedirect: '/login',

      // mins
      refreshInterval: 10,
      forbiddenRedirect: '/403',
      notFoundRedirect: '/404',
      allowThirdpartyLogin: false,
    };
    this.options = Object.assign({}, defaultOptions, opts);
    if (!this.options.fetchUser) {
      this.options.fetchUser = this.url('me');
    }

    const { options } = this;
    const instance = (Vue.$http || Vue.prototype.$http).create({
      headers: {
        'content-type': options.contentType || 'application/json;charset=utf-8',
      },
    });

    // authRedirect: 这里应该使用绝对路径，否则特殊情况会陷入无限循环
    // 可以使用 opts.absPath = false 来避免此限制
    if (options.absPath && options.authRedirect && options.authRedirect[0] !== '/') {
      console.error('The `authRedirect` option needs to be an absolute path');
      return;
    }

    this._http = instance;
    this._store = Vue.store;
    this._router = Vue.router;

    // fetch 用户信息后，变为 true，开始刷新；退出时，置为 false；
    this._loaded = false;

    // 下面用到此变量的地方均是对小程序和移动端的特殊处理
    this.isMobileOrMiniprogram = !!navigator.userAgent.match(/mobile|miniProgram|android/i);

    // add consts
    this.consts = consts;

    // register store
    Vue.store.registerModule('auth', store);

    // 必须在 register store 后面执行
    this._store.commit(types.SET_APPKEY, options.appKey);

    // register http interceptors
    this._http.interceptors.request.use((request) => {
      // 判断是否是 hamlet 请求，如果是添加 app_key
      if (request.url.indexOf(options.hamletPrefix) !== -1) {
        const appKey = this._store.state.auth.appKey;
        const method = request.method && request.method.toUpperCase();
        if (['POST', 'PUT'].indexOf(method) !== -1) {
          request.data = Object.assign(
            request.data || {},
            { app_key: appKey },
          );
        } else if (['GET'].indexOf(method) !== -1) {
          request.params = Object.assign(
            request.params || {},
            { app_key: appKey },
          );
        }
      }

      // 判断是否包含 token，如果有，则加到 header 里面
      const token = this._store.state.auth.token;

      if (token) {
        request.headers.Authorization = `${options.authType} ${token}`;
        // this._http.defaults.headers.common['Authorization'] = `${options.authType} ${token}`;
      }

      return request;
    });

    this._http.interceptors.response.use((res) => {
      // 当发现返回码为 401 时，跳转到登陆页面
      if (res.status === 401 && (!res.config || !res.config.headers || !res.config.headers._noauth)) {
        console.debug('unauthorized, jump to login page');
        this._router.push(options.authRedirect);
      }

      return Promise.resolve(res);
    }, (error) => {
      console.error('error', error);
      return Promise.reject(error);
    });

    // 记录上一次的 URL, 避免在 token 失效且重定向的 URL 非绝对路径时陷入无限循环
    let latestUrl = null;

    // 注册路由，实现当跳转到需要验证的 URL 时，自动检查认证状态，如果失败，跳转到登录页面
    Vue.router.beforeEach((to, from, next) => {
      if (latestUrl && latestUrl === to.path) {
        next();
      } else {
        latestUrl = to.path;
      }

      const query = to.query;
      if (query && query.tk) {
        const token = query.tk;
        localStorage.setItem('token', token);
        this._store.commit(types.SET_TOKEN, token);
      }

      if (query && query.rtk) {
        const refreshToken = query.rtk;
        localStorage.setItem('refresh_token', refreshToken);
        this._store.commit(types.SET_REFRESH_TOKEN, refreshToken);
      }

      // not matched route redirect to notFound route
      console.log('>>>> to: ', to);
      if (!to.matched.length) {
        next(options.notFoundRedirect);
      }

      let auth = false;
      const authRoutes = to.matched.filter(route => 'auth' in route.meta);
      if (authRoutes.length) {
        auth = authRoutes[authRoutes.length - 1].meta.auth;
      }

      // 当用户通过第三方登录时,拿到 URL 中的 token 和 access_token, 存入 localstorage, 重定向至去除 token 信息的 URL
      if (options.allowThirdpartyLogin &&
        query.thirdparty_connect_access_token &&
        query.thirdparty_connect_refresh_token) {
        this._store.commit(types.SET_TOKEN, query.thirdparty_connect_access_token);
        this._store.commit(types.SET_REFRESH_TOKEN, query.thirdparty_connect_refresh_token);
        next({ path: to.path });
      }

      // 当用户绑定第三方账号时, 重定向至去除绑定成功信息的 URL
      if (options.allowThirdpartyLogin && query.thirdparty_connect_ok) {
        next({ path: to.path });
      }

      const token = this._store.state.auth.token;

      // 需要认证时，检查权限是否满足
      if (auth) {
        // 未获得 token
        if (!token) {
          // 当路由重定向到登陆页面，附带重定向的页面值
          let fullPath = to.path;
          if (query && fullPath) {
            const keys = Object.keys(query);
            for (let i = 0, len = keys.length; i < len; i += 1) {
              fullPath = `${fullPath}${i === 0 ? '?' : '&'}${keys[i]}=${query[keys[i]]}`;
            }
          }
          next({ path: options.authRedirect, query: { redirectedFrom: to.redirectedFrom || fullPath } });
        } else {
          this.fetch().then(() => {
            const user = this.user();
            if (typeof auth === 'object' && auth.constructor === Array) {
              if ((!user.role && !auth.length) || auth.indexOf(user.role) !== -1) {
                next();
              } else {
                next(options.forbiddenRedirect);
              }
            } else if (this.isMobileOrMiniprogram || auth === user.role || auth === true) {
              next();
            } else {
              next(options.authRedirect);
            }
          }, () => {
            // 获取用户信息失败，跳转到登录页面
            if (this.isMobileOrMiniprogram) {
              next();
            } else {
              next(options.authRedirect);
            }
          });
        }
      } else if (token) {
        // 不需要认证时，如果存在 token，则更新一次用户信息
        this.fetch().then(() => {
          // token 有效，跳转
          next();
        }).catch(() => {
          // token 失效，清除 token
          // hack: 小程序、移动端这里不需要清空 token
          if (!this.isMobileOrMiniprogram) {
            this._store.commit(types.CLEAR_TOKENS);
          }

          next();
        });
      } else {
        next();
      }
    });

    // refresh
    setInterval(() => {
      if (this._loaded) {
        this.refresh();
      }
    }, options.refreshInterval * 60 * 1000);
  }

  url(relative) {
    let prefix = this.options.hamletPrefix;

    if (prefix.charAt(prefix.length - 1) === '/') {
      prefix = prefix.slice(0, -1);
    }

    if (relative.charAt(0) === '/') {
      relative = relative.slice(1);
    }

    return `${prefix}/${relative}`;
  }

  ready() {
    return !!this._store.state.auth.user;
  }

  login({ username, password }) {
    const url = this.url('login');
    const _this = this;
    const __randNum = Math.random();
    return this._http.post(url,
      { username, password },
      { params: { __randNum } },
    )
      .then((res) => {
        // console.log('\n\n>>>login successfully>>>\n\n', res);
        const data = res.data.data;

        if (res.data.ok) {
          // 强制微信绑定的应用只返回 `auth_key`
          if (data.auth_key) {
            return res;
          }

          _this._store.commit(types.SET_TOKEN, data.access_token);
          _this._store.commit(types.SET_REFRESH_TOKEN, data.refresh_token);

          // 提示微信绑定
          if (data.prompt_bind_wechat) {
            return res;
          }

          // 正常登录时自动获取 user 信息
          return _this.fetch();
        }

        return Promise.reject(res);
      });
  }

  user() {
    return this._store.state.auth.user || {};
  }

  token() {
    return this._store.state.auth.token;
  }

  fetch() {
    const url = this.options.fetchUser;
    const _this = this;
    const __randNum = Math.random();
    return this._http.get(url, {
      transformRequest(req, headers) {
        headers._noauth = true;
        return req;
      },
      params: { __randNum },
    })
      .then((res) => {
        if (res.data.ok) {
          _this._store.commit(types.SET_USER, res.data.data.user);
          _this._loaded = true;
          return res;
        }

        return Promise.reject(res);
      });
  }

  logout() {
    const url = this.url('logout');
    const _this = this;
    const __randNum = Math.random();
    return this._http.post(url, {}, { params: { __randNum } })
      .then(() => {
        _this._store.commit(types.CLEAR_TOKENS);
        _this._loaded = false;
      }, (res) => {
        console.warn('logout failed', res);
      });
  }

  refresh() {
    const url = this.url('refresh');
    const _this = this;
    const __randNum = Math.random();
    return this._http.get(url, {
      params: {
        refresh_token: _this._store.state.auth.refresh_token,
        __randNum,
      },
    })
      .then((res) => {
        const { data } = res;
        if (data.ok) {
          _this._store.commit(types.SET_TOKEN, data.data.access_token);
          return res;
        }

        return Promise.reject(res);
      }, (res) => {
        console.warn('refresh token failed,', res);
      });
  }

  changePassword({ current_password, password, confirm_password }) {
    const url = this.url('change_password');
    const _this = this;
    const __randNum = Math.random();
    return this._http.post(url,
      { current_password, password, confirm_password },
      { params: { __randNum } },
    )
      .then((res) => {
        console.log('changePassword successfully', res);
        if (res.data.ok) {
          return res;
        }

        return Promise.reject(res);
      });
  }
}
<commit_msg>feat: can use 'readAppToken' to get tokens from other apps<commit_after>/**
 * Created by zhangjinjie on 2017/5/17.
 */
import store from './store';
import * as types from './store/types';
import consts from './consts';

export default class Auth {
  constructor(Vue, opts = {}) {
    // check Axios, Vue-router, Vuex used
    if (!Vue.$http && !Vue.prototype.$http) {
      console.error('Axios must be set first');
      return;
    }

    if (!Vue.router) {
      console.error('Vue router 2.x must be set first');
      return;
    }

    if (!Vue.store) {
      console.error('Vuex must be used first');
      return;
    }

    // app key
    if (!opts.appKey) {
      console.error('must set app key first');
      return;
    }

    // set variables;
    this._Vue = Vue;

    // options
    const defaultOptions = {
      absPath: true,
      appKey: '',
      authType: 'Bearer',
      hamletPrefix: '/api/auth',
      authRedirect: '/login',

      // mins
      refreshInterval: 10,
      forbiddenRedirect: '/403',
      notFoundRedirect: '/404',
      allowThirdpartyLogin: false,

      // 获取当前用户在当前 APP 中的 token 信息
      readAppToken: '/thirdparty/ym_app/token',

      //  父应用 URL，只有 parentOrigin 包含的 URL 才会通过父应用的 token 去获取当前应用的 token
      parentOrigin: {
        oneview: 'https://oneview.yimian.com.cn',
        'oneview-test': 'http://oneview.test.yimian.com.cn',
      },

      // 当前应用语言国际化时存储在 localStorage 里的 key
      langKey: 'VUE-HAMLET_LANGUAGE',
    };
    this.options = Object.assign({}, defaultOptions, opts);
    if (!this.options.fetchUser) {
      this.options.fetchUser = this.url('me');
    }

    const { options } = this;
    const instance = (Vue.$http || Vue.prototype.$http).create({
      headers: {
        'content-type': options.contentType || 'application/json;charset=utf-8',
      },
    });

    // authRedirect: 这里应该使用绝对路径，否则特殊情况会陷入无限循环
    // 可以使用 opts.absPath = false 来避免此限制
    if (options.absPath && options.authRedirect && options.authRedirect[0] !== '/') {
      console.error('The `authRedirect` option needs to be an absolute path');
      return;
    }

    this._http = instance;
    this._store = Vue.store;
    this._router = Vue.router;

    // fetch 用户信息后，变为 true，开始刷新；退出时，置为 false；
    this._loaded = false;

    // 下面用到此变量的地方均是对小程序和移动端的特殊处理
    this.isMobileOrMiniprogram = !!navigator.userAgent.match(/mobile|miniProgram|android/i);

    // add consts
    this.consts = consts;

    // register store
    Vue.store.registerModule('auth', store);

    // 必须在 register store 后面执行
    this._store.commit(types.SET_APPKEY, options.appKey);

    // register http interceptors
    this._http.interceptors.request.use((request) => {
      // 判断是否是 hamlet 请求，如果是添加 app_key
      if (request.url.indexOf(options.hamletPrefix) !== -1) {
        const appKey = this._store.state.auth.appKey;
        const method = request.method && request.method.toUpperCase();
        if (['POST', 'PUT'].indexOf(method) !== -1) {
          request.data = Object.assign(
            request.data || {},
            { app_key: appKey },
          );
        } else if (['GET'].indexOf(method) !== -1) {
          request.params = Object.assign(
            request.params || {},
            { app_key: appKey },
          );
        }
      }

      // 判断是否包含 token，如果有，则加到 header 里面
      const token = this._store.state.auth.token;

      if (token) {
        request.headers.Authorization = `${options.authType} ${token}`;
        // this._http.defaults.headers.common['Authorization'] = `${options.authType} ${token}`;
      }

      return request;
    });

    this._http.interceptors.response.use((res) => {
      // 当发现返回码为 401 时，跳转到登陆页面
      if (res.status === 401 && (!res.config || !res.config.headers || !res.config.headers._noauth)) {
        console.debug('unauthorized, jump to login page');
        this._router.push(options.authRedirect);
      }

      return Promise.resolve(res);
    }, (error) => {
      console.error('error', error);
      return Promise.reject(error);
    });

    // 记录上一次的 URL, 避免在 token 失效且重定向的 URL 非绝对路径时陷入无限循环
    let latestUrl = null;

    // 注册路由，实现当跳转到需要验证的 URL 时，自动检查认证状态，如果失败，跳转到登录页面
    Vue.router.beforeEach((to, from, next) => {
      if (latestUrl && latestUrl === to.path) {
        next();
      } else {
        latestUrl = to.path;
      }

      const query = to.query;
      if (query && query.tk) {
        const token = query.tk;
        localStorage.setItem('token', token);
        this._store.commit(types.SET_TOKEN, token);
      }

      if (query && query.rtk) {
        const refreshToken = query.rtk;
        localStorage.setItem('refresh_token', refreshToken);
        this._store.commit(types.SET_REFRESH_TOKEN, refreshToken);
      }

      // not matched route redirect to notFound route
      console.log('>>>> to: ', to);
      // 嵌入到 iFrame，此时 `document.referrer` 不为空
      const turl = document.referrer;
      // console.log('hamlet---turl', turl);
      if (turl) {
        // console.log('this.options.parentOrigin--obj---', this.options.parentOrigin);
        const keys = Object.keys(options.parentOrigin || {});
        let flag = false;

        for (let i = 0, len = keys.length; i < len; i += 1) {
          const re = new RegExp(`^${options.parentOrigin[keys[i]]}`, 'g');
          if (turl.match(re)) {
            flag = true;
            break;
          }
        }

        if (flag) {
          // console.log('---vue-hamlet-location', JSON.parse(JSON.stringify(location)));
          try {
            // 嵌入了父应用的 token、app_key，就先获取这个用户在当前 APP（如果存在）的 token 信息
            if (query.token && query.parent_app_key) {
              this._store.commit(types.SET_TOKEN, query.token);

              // langKey 为空：不使用「语言切换」
              if (this.options.langKey) {
                const lang = query.lang === 'en' ? 'en' : 'zh-CN'
                this._$locale.use(lang);
                localStorage.setItem(options.langKey, lang);
              }

              return this.readAppUserToken({
                app_key: query.parent_app_key,
                child_app_key: options.appKey,
              })
                .then((val) => {
                  const { data } = val.data;
                  this._store.commit(types.SET_TOKEN, data.access_token);
                  this._store.commit(types.SET_REFRESH_TOKEN, data.refresh_token);
                  this.checkAuth(to, query, next);
                })
                .catch((res) => {
                  console.log('Failed to get token', res);
                  next(options.notFoundRedirect);
                });
            }
          } catch (err) {
            console.log('err', err);
            next(options.notFoundRedirect);
          }
        }
      }

      this.checkAuth(to, query, next);
    });

    // refresh
    setInterval(() => {
      if (this._loaded) {
        this.refresh();
      }
    }, options.refreshInterval * 60 * 1000);
  }

  url(relative) {
    let prefix = this.options.hamletPrefix;

    if (prefix.charAt(prefix.length - 1) === '/') {
      prefix = prefix.slice(0, -1);
    }

    if (relative.charAt(0) === '/') {
      relative = relative.slice(1);
    }

    return `${prefix}/${relative}`;
  }

  ready() {
    return !!this._store.state.auth.user;
  }

  login({ username, password }) {
    const url = this.url('login');
    const _this = this;
    const __randNum = Math.random();
    return this._http.post(url,
      { username, password },
      { params: { __randNum } },
    )
      .then((res) => {
        // console.log('\n\n>>>login successfully>>>\n\n', res);
        const data = res.data.data;

        if (res.data.ok) {
          // 强制微信绑定的应用只返回 `auth_key`
          if (data.auth_key) {
            return res;
          }

          _this._store.commit(types.SET_TOKEN, data.access_token);
          _this._store.commit(types.SET_REFRESH_TOKEN, data.refresh_token);

          // 提示微信绑定
          if (data.prompt_bind_wechat) {
            return res;
          }

          // 正常登录时自动获取 user 信息
          return _this.fetch();
        }

        return Promise.reject(res);
      });
  }

  user() {
    return this._store.state.auth.user || {};
  }

  token() {
    return this._store.state.auth.token;
  }

  fetch() {
    const url = this.options.fetchUser;
    const _this = this;
    const __randNum = Math.random();
    return this._http.get(url, {
      transformRequest(req, headers) {
        headers._noauth = true;
        return req;
      },
      params: { __randNum },
    })
      .then((res) => {
        if (res.data.ok) {
          _this._store.commit(types.SET_USER, res.data.data.user);
          _this._loaded = true;
          return res;
        }

        return Promise.reject(res);
      });
  }

  logout() {
    const url = this.url('logout');
    const _this = this;
    const __randNum = Math.random();
    return this._http.post(url, {}, { params: { __randNum } })
      .then(() => {
        _this._store.commit(types.CLEAR_TOKENS);
        _this._loaded = false;
      }, (res) => {
        console.warn('logout failed', res);
      });
  }

  refresh() {
    const url = this.url('refresh');
    const _this = this;
    const __randNum = Math.random();
    return this._http.get(url, {
      params: {
        refresh_token: _this._store.state.auth.refresh_token,
        __randNum,
      },
    })
      .then((res) => {
        const { data } = res;
        if (data.ok) {
          _this._store.commit(types.SET_TOKEN, data.data.access_token);
          return res;
        }

        return Promise.reject(res);
      }, (res) => {
        console.warn('refresh token failed,', res);
      });
  }

  changePassword({ current_password, password, confirm_password }) {
    const url = this.url('change_password');
    const _this = this;
    const __randNum = Math.random();
    return this._http.post(url,
      { current_password, password, confirm_password },
      { params: { __randNum } },
    )
      .then((res) => {
        console.log('changePassword successfully', res);
        if (res.data.ok) {
          return res;
        }

        return Promise.reject(res);
      });
  }

  readAppUserToken(params) {
    const url = this.options.readAppToken;
    const __randNum = Math.random();
    return this._http.post(url,
      params,
      { params: { __randNum } },
    )
      .then((res) => {
        console.log('readAppUserToken successfully', res);
        if (res.data.ok) {
          return res;
        }

        return Promise.reject(res);
      });
  }

  checkAuth(to, query, next) {
    if (!to.matched.length) {
      next(options.notFoundRedirect);
    }

    let auth = false;
    const authRoutes = to.matched.filter(route => 'auth' in route.meta);
    if (authRoutes.length) {
      auth = authRoutes[authRoutes.length - 1].meta.auth;
    }

    // 当用户通过第三方登录时,拿到 URL 中的 token 和 access_token, 存入 localstorage, 重定向至去除 token 信息的 URL
    if (options.allowThirdpartyLogin &&
      query.thirdparty_connect_access_token &&
      query.thirdparty_connect_refresh_token) {
      this._store.commit(types.SET_TOKEN, query.thirdparty_connect_access_token);
      this._store.commit(types.SET_REFRESH_TOKEN, query.thirdparty_connect_refresh_token);
      next({ path: to.path });
    }

    // 当用户绑定第三方账号时, 重定向至去除绑定成功信息的 URL
    if (options.allowThirdpartyLogin && query.thirdparty_connect_ok) {
      next({ path: to.path });
    }

    const token = this._store.state.auth.token;

    // 需要认证时，检查权限是否满足
    if (auth) {
      // 未获得 token
      if (!token) {
        // 当路由重定向到登陆页面，附带重定向的页面值
        let fullPath = to.path;
        if (query && fullPath) {
          const keys = Object.keys(query);
          for (let i = 0, len = keys.length; i < len; i += 1) {
            fullPath = `${fullPath}${i === 0 ? '?' : '&'}${keys[i]}=${query[keys[i]]}`;
          }
        }
        next({ path: options.authRedirect, query: { redirectedFrom: to.redirectedFrom || fullPath } });
      } else {
        this.fetch().then(() => {
          const user = this.user();
          if (typeof auth === 'object' && auth.constructor === Array) {
            if ((!user.role && !auth.length) || auth.indexOf(user.role) !== -1) {
              next();
            } else {
              next(options.forbiddenRedirect);
            }
          } else if (this.isMobileOrMiniprogram || auth === user.role || auth === true) {
            next();
          } else {
            next(options.authRedirect);
          }
        }, () => {
          // 获取用户信息失败，跳转到登录页面
          if (this.isMobileOrMiniprogram) {
            next();
          } else {
            next(options.authRedirect);
          }
        });
      }
    } else if (token) {
      // 不需要认证时，如果存在 token，则更新一次用户信息
      this.fetch().then(() => {
        // token 有效，跳转
        next();
      }).catch(() => {
        // token 失效，清除 token
        // hack: 小程序、移动端这里不需要清空 token
        if (!this.isMobileOrMiniprogram) {
          this._store.commit(types.CLEAR_TOKENS);
        }

        next();
      });
    } else {
      next();
    }
  }
}
<|endoftext|>


<commit_before>window.addEventListener('load',function() {

  video = document.getElementById('video');
  playButton = document.getElementById('play-button');
  pauseScreen = document.getElementById('screen');
  screenButton = document.getElementById('screen-button');
  progressbarContainer = document.getElementById('progressbar-container');
  progressbar = document.getElementById('progressbar');
  timeField = document.getElementById('time-field');
  soundButton = document.getElementById('sound-button');
  sbarContainer = document.getElementById('sbar-container');
  sbar = document.getElementById('sbar');
  fullscreenButton = document.getElementById('fullscreen-button');
  video.load();
  video.addEventListener('canplay',function() {

    playButton.addEventListener('click',playOrPause, false);
    progressbarContainer.addEventListener('click',skip, false);
    updatePlayer();
    soundButton.addEventListener('click', muteOrUnmute, false);
    sbarContainer.addEventListener('click', changeVolume, false);
    fullscreenButton.addEventListener('click', fullscreen, false);
    screenButton.addEventListener('click', playOrPause, false)

  }, false);

}, false);

function playOrPause() {
  if(video.paused) {
    video.play();
    playButton.src = 'images/pause.png';
    update = setInterval(updatePlayer,30);
    pauseScreen.style.display = 'none';

  } else {
    video.pause();
    playButton.src = 'images/play.png';
    window.clearInterval(update);
    pauseScreen.style.display = 'block';
    screenButton.src = 'images/play.png';
  }
}

function updatePlayer() {
  var percentage = (video.currentTime/video.duration)*100;
  progressbar.style.width=percentage+'%';
  timeField.innerHTML = getFormattedTime();
  if(video.ended) {
    window.clearInterval(update);
    playButton.src = 'images/replay.png';
    pauseScreen.style.display = 'block';
    screenButton.src = 'images/replay.png';
  } else if (video.paused) {
    playButton.src = 'images/play.png';
    screenButton.src = 'images/play.png';
  }

}

function skip(event) {
  var mouseX = event.pageX - progressbar.offsetLeft;
  var width = window.getComputedStyle(progressbarContainer).getPropertyValue('width');
  width = parseFloat(width.substr(0, width.length-2));
  video.currentTime = (mouseX/width)*video.duration;
  updatePlayer();
}

function getFormattedTime() {
  var seconds = Math.round(video.currentTime);
  var minutes = Math.floor(seconds/60);
  if(minutes > 0) seconds -= minutes*60;
  if(seconds.toString().length===1) seconds = '0'+seconds;

  var totalSeconds = Math.round(video.duration);
  var totalMinutes = Math.floor(totalSeconds/60);
  if(totalMinutes > 0) totalSeconds -= totalMinutes*60;
  if(totalSeconds.toString().length===1) totalSeconds = '0'+totalSeconds;

  return minutes+':'+seconds+' / '+totalMinutes+':'+totalSeconds;
}

function muteOrUnmute() {
  if (!video.muted) {
    video.muted = true;
    soundButton.src = 'images/mute.png';
    sbar.style.display='none';
  } else {
    video.muted =  false;
    soundButton.src = 'images/sound.png';
    sbar.style.display='block';
  }
}

function changeVolume(event) {
    var mouseX = event.pageX - sbarContainer.offsetLeft;
    var width = window.getComputedStyle(sbarContainer).getPropertyValue('width');
    width = parseFloat(width.substr(0, width.length-2));

    video.volume = (mouseX/width);
    sbar.style.width = (mouseX/width)*100+'%';
    video.muted =  false;
    soundButton.src = 'images/sound.png';
    sbar.style.display='block';
}

function fullscreen() {
  if(video.requestFullscreen) {
    video.requestFullscreen();
  } else if(video.webkitRequestFullscreen) {
    video.webkitRequestFullscreen();
  } else if(video.mozRequestFullscreen) {
    video.mozRequestFullscreen();
  } else if(video.msRequestFullscreen) {
    video.msRequestFullscreen();
  }
}
<commit_msg>variables<commit_after>window.addEventListener('load',function() {
  //-variables
  video = document.getElementById('video');
  playButton = document.getElementById('play-button');
  pauseScreen = document.getElementById('screen');
  screenButton = document.getElementById('screen-button');
  progressbarContainer = document.getElementById('progressbar-container');
  progressbar = document.getElementById('progressbar');
  timeField = document.getElementById('time-field');
  soundButton = document.getElementById('sound-button');
  sbarContainer = document.getElementById('sbar-container');
  sbar = document.getElementById('sbar');
  fullscreenButton = document.getElementById('fullscreen-button');
  video.load();
  video.addEventListener('canplay',function() {

    playButton.addEventListener('click',playOrPause, false);
    progressbarContainer.addEventListener('click',skip, false);
    updatePlayer();
    soundButton.addEventListener('click', muteOrUnmute, false);
    sbarContainer.addEventListener('click', changeVolume, false);
    fullscreenButton.addEventListener('click', fullscreen, false);
    screenButton.addEventListener('click', playOrPause, false)

  }, false);

}, false);

function playOrPause() {
  if(video.paused) {
    video.play();
    playButton.src = 'images/pause.png';
    update = setInterval(updatePlayer,30);
    pauseScreen.style.display = 'none';

  } else {
    video.pause();
    playButton.src = 'images/play.png';
    window.clearInterval(update);
    pauseScreen.style.display = 'block';
    screenButton.src = 'images/play.png';
  }
}

function updatePlayer() {
  var percentage = (video.currentTime/video.duration)*100;
  progressbar.style.width=percentage+'%';
  timeField.innerHTML = getFormattedTime();
  if(video.ended) {
    window.clearInterval(update);
    playButton.src = 'images/replay.png';
    pauseScreen.style.display = 'block';
    screenButton.src = 'images/replay.png';
  } else if (video.paused) {
    playButton.src = 'images/play.png';
    screenButton.src = 'images/play.png';
  }

}

function skip(event) {
  var mouseX = event.pageX - progressbar.offsetLeft;
  var width = window.getComputedStyle(progressbarContainer).getPropertyValue('width');
  width = parseFloat(width.substr(0, width.length-2));
  video.currentTime = (mouseX/width)*video.duration;
  updatePlayer();
}

function getFormattedTime() {
  var seconds = Math.round(video.currentTime);
  var minutes = Math.floor(seconds/60);
  if(minutes > 0) seconds -= minutes*60;
  if(seconds.toString().length===1) seconds = '0'+seconds;

  var totalSeconds = Math.round(video.duration);
  var totalMinutes = Math.floor(totalSeconds/60);
  if(totalMinutes > 0) totalSeconds -= totalMinutes*60;
  if(totalSeconds.toString().length===1) totalSeconds = '0'+totalSeconds;

  return minutes+':'+seconds+' / '+totalMinutes+':'+totalSeconds;
}

function muteOrUnmute() {
  if (!video.muted) {
    video.muted = true;
    soundButton.src = 'images/mute.png';
    sbar.style.display='none';
  } else {
    video.muted =  false;
    soundButton.src = 'images/sound.png';
    sbar.style.display='block';
  }
}

function changeVolume(event) {
    var mouseX = event.pageX - sbarContainer.offsetLeft;
    var width = window.getComputedStyle(sbarContainer).getPropertyValue('width');
    width = parseFloat(width.substr(0, width.length-2));

    video.volume = (mouseX/width);
    sbar.style.width = (mouseX/width)*100+'%';
    video.muted =  false;
    soundButton.src = 'images/sound.png';
    sbar.style.display='block';
}

function fullscreen() {
  if(video.requestFullscreen) {
    video.requestFullscreen();
  } else if(video.webkitRequestFullscreen) {
    video.webkitRequestFullscreen();
  } else if(video.mozRequestFullscreen) {
    video.mozRequestFullscreen();
  } else if(video.msRequestFullscreen) {
    video.msRequestFullscreen();
  }
}
<|endoftext|>


<commit_before>// Copyright 2015, University of Colorado Boulder

/**
 * Tandem is used to assign unique identifiers to instances in PhET simulations and register/unregister them in a
 * registry. It is used to support PhET-iO.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */
define( function( require ) {
  'use strict';

  // modules
  var inherit = require( 'PHET_CORE/inherit' );
  var tandemNamespace = require( 'TANDEM/tandemNamespace' );
  var toCamelCase = require( 'PHET_CORE/toCamelCase' );

  // text
  var packageString = require( 'text!REPOSITORY/package.json' );

  // constants
  var packageJSON = JSON.parse( packageString ); // Tandem can't depend on joist, so requiring packageJSON doesn't work
  var PHET_IO_ENABLED = !!( window.phet && window.phet.phetio );

  // Listeners that will be notified when items are registered/deregistered. See doc in addInstanceListener
  var instanceListeners = [];

  // variables
  // Before listeners are wired up, tandems are buffered.  When listeners are wired up, Tandem.launch() is called
  // and buffered tandems are flushed, then subsequent tandems are delivered to listeners directly
  var launched = false;
  var bufferedElements = [];

  // increment names of uninstrumented common code tandems to avoid collisions for uninstrumented sims with
  // phetioValidateTandems=false
  var uninstrumentedCodeIndex = 0;

  /**
   * Typically, sims will create tandems using `tandem.createTandem`.  This constructor is used internally or when
   * a tandem must be created from scratch.
   *
   * @param {string} phetioID - unique identifier as a string ('' for a root id)
   * @param {Object} [options]
   * @constructor
   */
  function Tandem( phetioID, options ) {

    // options (even subtype options) must be stored on the instance so they can be passed through to children
    // Note: Make sure that added options here are also added to options for inheritance and/or
    // for composition (createTandem/parentTandem) as they make sense.
    options = _.extend( {

      // Enabled tandems notify listeners when they are added. Disabled tandems do not notify listeners,
      // but children of a disabled tandem may be enabled.
      enabled: true,

      // if the tandem is not supplied and required, an error will be thrown.
      supplied: true,

      // required === false means it is an optional tandem
      required: true
    }, options );

    // @public (read-only)
    this.phetioID = ( phetioID !== undefined ) ? phetioID : '';

    // @private
    this.required = options.required;

    // @private
    this.supplied = options.supplied;

    // @private
    this.enabled = options.enabled;
  }

  tandemNamespace.register( 'Tandem', Tandem );

  inherit( Object, Tandem, {

    /**
     * Adds an instance of any type.  For example, it could be an axon Property, scenery Node or Sun button.  Each
     * item should only be added to the registry once, but that is not enforced here in Tandem.  For PhET-iO, phetio.js
     * enforces one entry per ID in phetio.addInstance
     *
     * This is used to register instances with PhET-iO.
     * @param {Object} instance - the instance to add
     * @public
     */
    addInstance: function( instance ) {

      if ( PHET_IO_ENABLED && this.enabled ) {

        // Throw an error if the tandem is required but not supplied
        if ( phet.phetio.queryParameters.phetioValidateTandems ) {
          assert && assert( !( this.required && !this.supplied ), 'Tandem was required but not supplied' );
        }

        // ValidateTandems is false and printMissingTandems flag is present for a tandem that is required but not supplied.
        if ( phet.phetio.queryParameters.printMissingTandems && ( this.required && !this.supplied ) ) {
          console.log( 'Required Tandem not supplied.\n' +
                       'this.phetioID = ' + this.phetioID + '\n' +
                       'Stack trace: ' + new Error().stack );
        }

        // If tandem is optional, then don't add the instance
        if ( !this.required && !this.supplied ) {
          if ( phet.phetio.queryParameters.printMissingTandems ) {
            var stackTrace = new Error().stack;

            // Generally Font is not desired because there are so many untandemized instances.
            if ( stackTrace.indexOf( 'PhetFont' ) === -1 ) {
              console.log( 'Optional Tandem not supplied.\n' +
                           'this.phetioID = ' + this.phetioID + '\n' +
                           'Stack trace: ' + stackTrace );
            }
          }

          // For optionally instrumented types that are not provided tandems, the instance isn't really "added"
          // but likewise, it in not an error
          return;
        }

        if ( !launched ) {
          bufferedElements.push( instance );
        }
        else {
          for ( var i = 0; i < instanceListeners.length; i++ ) {
            instanceListeners[ i ].addInstance( instance );
          }
        }
      }
    },

    /**
     * Removes an instance from the registry
     * @param {Object} instance - the instance to remove
     * @public
     */
    removeInstance: function( instance ) {
      if ( !this.required && !this.supplied ) {
        return;
      }

      // Only active when running as phet-io
      if ( PHET_IO_ENABLED && this.enabled ) {
        for ( var i = 0; i < instanceListeners.length; i++ ) {
          instanceListeners[ i ].removeInstance( this.phetioID, instance );
        }
      }
    },

    /**
     * Create a new Tandem by appending the given id
     * @param {string} id
     * @param {Object} [options]
     * @returns {Tandem}
     * @public
     */
    createTandem: function( id, options ) {

      // Make sure the id was provided
      assert && assert( typeof id === 'string' && id.length > 0, 'id must be defined' );

      var string = ( this.phetioID.length > 0 ) ? PhetioIDUtils.append( this.phetioID, id ) : id;

      // Any child of something should be passed all inherited options. Make sure that this extend call includes all
      // that make sense from the constructor's extend call.
      options = _.extend( {
        enabled: this.enabled,
        supplied: this.supplied,
        required: this.required
      }, options );

      return new Tandem( string, options );
    },

    /**
     * Creates a group tandem for creating multiple indexed child tandems, such as:
     * sim.screen.model.electron_0
     * sim.screen.model.electron_1
     *
     * In this case, 'sim.screen.model.electron' is the string passed to createGroupTandem.
     *
     * Used for arrays, observable arrays, or when many elements of the same type are created and they do not otherwise
     * have unique identifiers.
     * @param id
     * @returns {GroupTandem}
     * @public
     */
    createGroupTandem: function( id ) {

      // Unfortunately we must resort to globals here since loading through the namespace would create a cycle
      return new GroupTandem( PhetioIDUtils.append( this.phetioID, id ) );
    },

    /**
     * Get the last part of the tandem (after the last .), used in Joist for creating button names dynamically based
     * on screen names
     * @returns {string} the tail of the tandem
     * @public
     */
    get tail() { // TODO: rename to getComponentName()
      return PhetioIDUtils.getComponentName( this.phetioID );
    },

    /**
     * Returns a Tandem for everything except the tail.
     * @returns {Tandem}
     * @public
     */
    get parentTandem() {
      var headID = PhetioIDUtils.getParentID( this.phetioID );

      return new Tandem( headID, {
        required: this.required,
        supplied: this.supplied,
        enabled: this.enabled
      } );
    },

    /**
     * Return true if this tandem is supplied and the sim is running in PhET-iO mode.
     * @returns {boolean}
     * @public
     */
    isSuppliedAndEnabled: function() {
      return this.supplied && PHET_IO_ENABLED;
    }
  }, {

    /**
     * Adds a listener that will be notified when items are registered/deregistered
     * Listeners have the form
     * {
     *   addInstance(id,instance),
     *   removeInstance(id,instance)
     * }
     * where id is of type {string} and instance is of type {Object}
     *
     * @param {Object} instanceListener - described above
     * @public
     * @static
     */
    addInstanceListener: function( instanceListener ) {
      instanceListeners.push( instanceListener );
    },

    /**
     * When all listeners are listening, all buffered instances are registered.
     * @public
     * @static
     */
    launch: function() {
      assert && assert( !launched, 'Tandem was launched twice' );
      launched = true;
      while ( bufferedElements.length > 0 ) {
        var element = bufferedElements.shift();
        element.phetioObjectTandem.addInstance( element );
      }
    },

    /**
     * Catch cases where tandem is being supplied to a class that doesn't support tandem.
     * @param {Object} [options]
     * @public
     * @static
     */
    disallowTandem: function( options ) {

      if ( Tandem.validationEnabled() ) {
        assert && assert( !options.tandem, 'tandem is not allowed' );
      }
    },

    /**
     * When running in PhET-iO brand, some code (such as user interface components) must be instrumented for PhET-iO.
     * Uninstrumented files should call this function to indicate they still need to be instrumented, so they aren't
     * missed.  See https://github.com/phetsims/phet-io/issues/668
     * @public
     * @static
     */
    indicateUninstrumentedCode: function() {

      // Guard against undefined errors
      if ( PHET_IO_ENABLED ) {

        // Assert if validating tandems
        if ( phet.phetio.queryParameters.phetioValidateTandems ) {
          assert && assert( false, 'Uninstrumented code detected' );
        }

        // Print stack trace if query parameter supplied
        if ( phet.phetio.queryParameters.printMissingTandems ) {
          var stackTrace = new Error().stack;
          console.log( 'Uninstrumented Code! Tandem not supplied: ' + ( uninstrumentedCodeIndex++ ) + '.\n' +
                       'Stack trace: ' + stackTrace );
        }
      }
    },

    /**
     * Determine whether or not tandem validation is turned on for the sim.
     * @returns {Boolean} If tandems are being validated or not.
     * @public
     * @static
     */
    validationEnabled: function() {
      return PHET_IO_ENABLED && phet.phetio.queryParameters.phetioValidateTandems;
    }
  } );

  // The next few statics are created outside the static block because they instantiate Tandem instances.

  /**
   * The root tandem for a simulation
   * @public
   * @static
   * @type {Tandem}
   */
  Tandem.rootTandem = new Tandem( toCamelCase( packageJSON.name ) );

  /**
   * Used to indicate a common code component that supports tandem, but doesn't not require it.
   * If a tandem is not passed through to this instance, then it will not be instrumented.
   * @public
   * @static
   * @type {Tandem}
   */
  Tandem.optional = Tandem.rootTandem.createTandem( 'optionalTandem', {
    required: false,
    supplied: false
  } );

  /**
   * Some common code (such as Checkbox or RadioButton) must always be instrumented and hence requires a tandem to be
   * passed in.
   * @public
   * @static
   * @type {Tandem}
   */
  Tandem.required = Tandem.rootTandem.createTandem( 'requiredTandem', {
    required: true,
    supplied: false
  } );

  /**
   * Group Tandem -- Declared in the same file to avoid circular reference errors in module loading.
   * @param {string} id - id as a string (or '' for a root id)
   * @constructor
   * @private create with Tandem.createGroupTandem
   */
  function GroupTandem( id ) {

    Tandem.call( this, id );

    // @private for generating indices from a pool
    this.groupElementIndex = 0;
  }

  tandemNamespace.register( 'Tandem.GroupTandem', GroupTandem );

  inherit( Tandem, GroupTandem, {

    /**
     * Creates the next tandem in the group.
     * @returns {Tandem}
     * @public
     */
    createNextTandem: function() {
      return new Tandem( this.phetioID + '_' + ( this.groupElementIndex++ ) );
    }
  } );

  return Tandem;
} );<commit_msg>Added assertions to Tandem.addInstance, see https://github.com/phetsims/phet-io/issues/1304<commit_after>// Copyright 2015, University of Colorado Boulder

/**
 * Tandem is used to assign unique identifiers to instances in PhET simulations and register/unregister them in a
 * registry. It is used to support PhET-iO.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 */
define( function( require ) {
  'use strict';

  // modules
  var inherit = require( 'PHET_CORE/inherit' );
  var tandemNamespace = require( 'TANDEM/tandemNamespace' );
  var toCamelCase = require( 'PHET_CORE/toCamelCase' );

  // text
  var packageString = require( 'text!REPOSITORY/package.json' );

  // constants
  var packageJSON = JSON.parse( packageString ); // Tandem can't depend on joist, so requiring packageJSON doesn't work
  var PHET_IO_ENABLED = !!( window.phet && window.phet.phetio );

  // Listeners that will be notified when items are registered/deregistered. See doc in addInstanceListener
  var instanceListeners = [];

  // variables
  // Before listeners are wired up, tandems are buffered.  When listeners are wired up, Tandem.launch() is called
  // and buffered tandems are flushed, then subsequent tandems are delivered to listeners directly
  var launched = false;
  var bufferedElements = [];

  // increment names of uninstrumented common code tandems to avoid collisions for uninstrumented sims with
  // phetioValidateTandems=false
  var uninstrumentedCodeIndex = 0;

  /**
   * Typically, sims will create tandems using `tandem.createTandem`.  This constructor is used internally or when
   * a tandem must be created from scratch.
   *
   * @param {string} phetioID - unique identifier as a string ('' for a root id)
   * @param {Object} [options]
   * @constructor
   */
  function Tandem( phetioID, options ) {

    // options (even subtype options) must be stored on the instance so they can be passed through to children
    // Note: Make sure that added options here are also added to options for inheritance and/or
    // for composition (createTandem/parentTandem) as they make sense.
    options = _.extend( {

      // Enabled tandems notify listeners when they are added. Disabled tandems do not notify listeners,
      // but children of a disabled tandem may be enabled.
      enabled: true,

      // if the tandem is not supplied and required, an error will be thrown.
      supplied: true,

      // required === false means it is an optional tandem
      required: true
    }, options );

    // @public (read-only)
    this.phetioID = ( phetioID !== undefined ) ? phetioID : '';

    // @private
    this.required = options.required;

    // @private
    this.supplied = options.supplied;

    // @private
    this.enabled = options.enabled;
  }

  tandemNamespace.register( 'Tandem', Tandem );

  inherit( Object, Tandem, {

    /**
     * Adds an instance of any type.  For example, it could be an axon Property, scenery Node or Sun button.  Each
     * item should only be added to the registry once, but that is not enforced here in Tandem.  For PhET-iO, phetio.js
     * enforces one entry per ID in phetio.addInstance
     *
     * This is used to register instances with PhET-iO.
     * @param {PhetioObject} instance - the instance to add
     * @public
     */
    addInstance: function( instance ) {

      assert && assert( arguments.length === 1, 'Tandem.addInstance takes one argument' );

      // Check that the instrumented instance is an instance of PhetioObject, cannot use typical require statement for
      // PhetioObject because it creates a module loading loop
      assert && assert( instance instanceof phet.tandem.PhetioObject, 'Instance should be of type PhetioObject' );

      if ( PHET_IO_ENABLED && this.enabled ) {

        // Throw an error if the tandem is required but not supplied
        if ( phet.phetio.queryParameters.phetioValidateTandems ) {
          assert && assert( !( this.required && !this.supplied ), 'Tandem was required but not supplied' );
        }

        // ValidateTandems is false and printMissingTandems flag is present for a tandem that is required but not supplied.
        if ( phet.phetio.queryParameters.printMissingTandems && ( this.required && !this.supplied ) ) {
          console.log( 'Required Tandem not supplied.\n' +
                       'this.phetioID = ' + this.phetioID + '\n' +
                       'Stack trace: ' + new Error().stack );
        }

        // If tandem is optional, then don't add the instance
        if ( !this.required && !this.supplied ) {
          if ( phet.phetio.queryParameters.printMissingTandems ) {
            var stackTrace = new Error().stack;

            // Generally Font is not desired because there are so many untandemized instances.
            if ( stackTrace.indexOf( 'PhetFont' ) === -1 ) {
              console.log( 'Optional Tandem not supplied.\n' +
                           'this.phetioID = ' + this.phetioID + '\n' +
                           'Stack trace: ' + stackTrace );
            }
          }

          // For optionally instrumented types that are not provided tandems, the instance isn't really "added"
          // but likewise, it in not an error
          return;
        }

        if ( !launched ) {
          bufferedElements.push( instance );
        }
        else {
          for ( var i = 0; i < instanceListeners.length; i++ ) {
            instanceListeners[ i ].addInstance( instance );
          }
        }
      }
    },

    /**
     * Removes an instance from the registry
     * @param {Object} instance - the instance to remove
     * @public
     */
    removeInstance: function( instance ) {
      if ( !this.required && !this.supplied ) {
        return;
      }

      // Only active when running as phet-io
      if ( PHET_IO_ENABLED && this.enabled ) {
        for ( var i = 0; i < instanceListeners.length; i++ ) {
          instanceListeners[ i ].removeInstance( this.phetioID, instance );
        }
      }
    },

    /**
     * Create a new Tandem by appending the given id
     * @param {string} id
     * @param {Object} [options]
     * @returns {Tandem}
     * @public
     */
    createTandem: function( id, options ) {

      // Make sure the id was provided
      assert && assert( typeof id === 'string' && id.length > 0, 'id must be defined' );

      var string = ( this.phetioID.length > 0 ) ? PhetioIDUtils.append( this.phetioID, id ) : id;

      // Any child of something should be passed all inherited options. Make sure that this extend call includes all
      // that make sense from the constructor's extend call.
      options = _.extend( {
        enabled: this.enabled,
        supplied: this.supplied,
        required: this.required
      }, options );

      return new Tandem( string, options );
    },

    /**
     * Creates a group tandem for creating multiple indexed child tandems, such as:
     * sim.screen.model.electron_0
     * sim.screen.model.electron_1
     *
     * In this case, 'sim.screen.model.electron' is the string passed to createGroupTandem.
     *
     * Used for arrays, observable arrays, or when many elements of the same type are created and they do not otherwise
     * have unique identifiers.
     * @param id
     * @returns {GroupTandem}
     * @public
     */
    createGroupTandem: function( id ) {

      // Unfortunately we must resort to globals here since loading through the namespace would create a cycle
      return new GroupTandem( PhetioIDUtils.append( this.phetioID, id ) );
    },

    /**
     * Get the last part of the tandem (after the last .), used in Joist for creating button names dynamically based
     * on screen names
     * @returns {string} the tail of the tandem
     * @public
     */
    get tail() { // TODO: rename to getComponentName()
      return PhetioIDUtils.getComponentName( this.phetioID );
    },

    /**
     * Returns a Tandem for everything except the tail.
     * @returns {Tandem}
     * @public
     */
    get parentTandem() {
      var headID = PhetioIDUtils.getParentID( this.phetioID );

      return new Tandem( headID, {
        required: this.required,
        supplied: this.supplied,
        enabled: this.enabled
      } );
    },

    /**
     * Return true if this tandem is supplied and the sim is running in PhET-iO mode.
     * @returns {boolean}
     * @public
     */
    isSuppliedAndEnabled: function() {
      return this.supplied && PHET_IO_ENABLED;
    }
  }, {

    /**
     * Adds a listener that will be notified when items are registered/deregistered
     * Listeners have the form
     * {
     *   addInstance(id,instance),
     *   removeInstance(id,instance)
     * }
     * where id is of type {string} and instance is of type {Object}
     *
     * @param {Object} instanceListener - described above
     * @public
     * @static
     */
    addInstanceListener: function( instanceListener ) {
      instanceListeners.push( instanceListener );
    },

    /**
     * When all listeners are listening, all buffered instances are registered.
     * @public
     * @static
     */
    launch: function() {
      assert && assert( !launched, 'Tandem was launched twice' );
      launched = true;
      while ( bufferedElements.length > 0 ) {
        var element = bufferedElements.shift();
        element.phetioObjectTandem.addInstance( element );
      }
    },

    /**
     * Catch cases where tandem is being supplied to a class that doesn't support tandem.
     * @param {Object} [options]
     * @public
     * @static
     */
    disallowTandem: function( options ) {

      if ( Tandem.validationEnabled() ) {
        assert && assert( !options.tandem, 'tandem is not allowed' );
      }
    },

    /**
     * When running in PhET-iO brand, some code (such as user interface components) must be instrumented for PhET-iO.
     * Uninstrumented files should call this function to indicate they still need to be instrumented, so they aren't
     * missed.  See https://github.com/phetsims/phet-io/issues/668
     * @public
     * @static
     */
    indicateUninstrumentedCode: function() {

      // Guard against undefined errors
      if ( PHET_IO_ENABLED ) {

        // Assert if validating tandems
        if ( phet.phetio.queryParameters.phetioValidateTandems ) {
          assert && assert( false, 'Uninstrumented code detected' );
        }

        // Print stack trace if query parameter supplied
        if ( phet.phetio.queryParameters.printMissingTandems ) {
          var stackTrace = new Error().stack;
          console.log( 'Uninstrumented Code! Tandem not supplied: ' + ( uninstrumentedCodeIndex++ ) + '.\n' +
                       'Stack trace: ' + stackTrace );
        }
      }
    },

    /**
     * Determine whether or not tandem validation is turned on for the sim.
     * @returns {Boolean} If tandems are being validated or not.
     * @public
     * @static
     */
    validationEnabled: function() {
      return PHET_IO_ENABLED && phet.phetio.queryParameters.phetioValidateTandems;
    }
  } );

  // The next few statics are created outside the static block because they instantiate Tandem instances.

  /**
   * The root tandem for a simulation
   * @public
   * @static
   * @type {Tandem}
   */
  Tandem.rootTandem = new Tandem( toCamelCase( packageJSON.name ) );

  /**
   * Used to indicate a common code component that supports tandem, but doesn't not require it.
   * If a tandem is not passed through to this instance, then it will not be instrumented.
   * @public
   * @static
   * @type {Tandem}
   */
  Tandem.optional = Tandem.rootTandem.createTandem( 'optionalTandem', {
    required: false,
    supplied: false
  } );

  /**
   * Some common code (such as Checkbox or RadioButton) must always be instrumented and hence requires a tandem to be
   * passed in.
   * @public
   * @static
   * @type {Tandem}
   */
  Tandem.required = Tandem.rootTandem.createTandem( 'requiredTandem', {
    required: true,
    supplied: false
  } );

  /**
   * Group Tandem -- Declared in the same file to avoid circular reference errors in module loading.
   * @param {string} id - id as a string (or '' for a root id)
   * @constructor
   * @private create with Tandem.createGroupTandem
   */
  function GroupTandem( id ) {

    Tandem.call( this, id );

    // @private for generating indices from a pool
    this.groupElementIndex = 0;
  }

  tandemNamespace.register( 'Tandem.GroupTandem', GroupTandem );

  inherit( Tandem, GroupTandem, {

    /**
     * Creates the next tandem in the group.
     * @returns {Tandem}
     * @public
     */
    createNextTandem: function() {
      return new Tandem( this.phetioID + '_' + ( this.groupElementIndex++ ) );
    }
  } );

  return Tandem;
} );<|endoftext|>


<commit_before>'use strict';

/**
 * @ngdoc function
 * @name otaniemi3dApp.controller:HeaderCtrl
 * @description
 * # HeaderCtrl
 * Controller of the otaniemi3dApp
 */
angular.module('otaniemi3dApp')
  .controller('HeaderCtrl', function ($scope, $location, $rootScope) {

    // Check if current URL-location matches the highlighted item
    $scope.isActive = function (viewLocation) {
      //startsWith()
        return $location.path().indexOf(viewLocation) === 0;
    };
    $scope.isCollapsed = true;

    $scope.$on('$routeChangeSuccess', function () {
        $rootScope.fullscreen = false;
    });

  });
<commit_msg>Optimize startsWith algorithm in HeaderCtrl<commit_after>'use strict';

/**
 * @ngdoc function
 * @name otaniemi3dApp.controller:HeaderCtrl
 * @description
 * # HeaderCtrl
 * Controller of the otaniemi3dApp
 */
angular.module('otaniemi3dApp')
  .controller('HeaderCtrl', function ($scope, $location, $rootScope) {

    // Check if current URL-location matches the highlighted item
    $scope.isActive = function (viewLocation) {
      //startsWith()
        return $location.path().lastIndexOf(viewLocation, 0) === 0;
    };
    $scope.isCollapsed = true;

    $scope.$on('$routeChangeSuccess', function () {
        $rootScope.fullscreen = false;
    });

  });
<|endoftext|>


<commit_before>const assert = require('assert')
const childProcess = require('child_process')
const fs = require('fs')
const http = require('http')
const multiparty = require('multiparty')
const path = require('path')
const temp = require('temp').track()
const url = require('url')
const {closeWindow} = require('./window-helpers')

const {remote} = require('electron')
const {app, BrowserWindow, crashReporter} = remote.require('electron')

describe('crashReporter module', () => {
  if (process.mas || process.env.DISABLE_CRASH_REPORTER_TESTS) return

  let originalTempDirectory = null
  let tempDirectory = null

  before(() => {
    tempDirectory = temp.mkdirSync('electronCrashReporterSpec-')
    originalTempDirectory = app.getPath('temp')
    app.setPath('temp', tempDirectory)
  })

  after(() => {
    app.setPath('temp', originalTempDirectory)
  })

  const fixtures = path.resolve(__dirname, 'fixtures')
  const generateSpecs = (description, browserWindowOpts) => {
    describe(description, () => {
      let w = null
      let stopServer = null

      beforeEach(() => {
        stopServer = null
        w = new BrowserWindow(Object.assign({ show: false }, browserWindowOpts))
      })

      afterEach(() => closeWindow(w).then(() => { w = null }))

      afterEach(() => {
        stopCrashService()
      })

      afterEach((done) => {
        if (stopServer != null) {
          stopServer(done)
        } else {
          done()
        }
      })

      it('should send minidump when renderer crashes', function (done) {
        // TODO(alexeykuzmin): Skip the test instead of marking it as passed.
        if (process.env.APPVEYOR === 'True') return done()
        if (process.env.TRAVIS === 'true') return done()

        this.timeout(120000)

        stopServer = startServer({
          callback (port) {
            const crashUrl = url.format({
              protocol: 'file',
              pathname: path.join(fixtures, 'api', 'crash.html'),
              search: '?port=' + port
            })
            w.loadURL(crashUrl)
          },
          processType: 'renderer',
          done: done
        })
      })
      it('should send minidump when node processes crash', function (done) {
        // TODO(alexeykuzmin): Skip the test instead of marking it as passed.
        if (process.env.APPVEYOR === 'True') return done()
        if (process.env.TRAVIS === 'true') return done()

        this.timeout(120000)

        stopServer = startServer({
          callback (port) {
            const crashesDir = path.join(app.getPath('temp'), `${process.platform === 'win32' ? 'Zombies' : app.getName()} Crashes`)
            const version = app.getVersion()
            const crashPath = path.join(fixtures, 'module', 'crash.js')

            if (process.platform === 'win32') {
              const crashServiceProcess = childProcess.spawn(process.execPath, [
                `--reporter-url=http://127.0.0.1:${port}`,
                '--application-name=Zombies',
                `--crashes-directory=${crashesDir}`
              ], {
                env: {
                  ELECTRON_INTERNAL_CRASH_SERVICE: 1
                },
                detached: true
              })
              remote.process.crashServicePid = crashServiceProcess.pid
            }

            childProcess.fork(crashPath, [port, version, crashesDir], {silent: true})
          },
          processType: 'browser',
          done: done
        })
      })
      it('should not send minidump if uploadToServer is false', function (done) {
        this.timeout(120000)

        let dumpFile
        let crashesDir = crashReporter.getCrashesDirectory()
        const existingDumpFiles = new Set()
        if (process.platform === 'darwin') {
          // crashpad puts the dump files in the "completed" subdirectory
          crashesDir = path.join(crashesDir, 'completed')
          crashReporter.setUploadToServer(false)
        }
        const testDone = (uploaded) => {
          if (uploaded) return done(new Error('Uploaded crash report'))
          if (process.platform === 'darwin') crashReporter.setUploadToServer(true)
          assert(fs.existsSync(dumpFile))
          done()
        }

        let pollInterval
        const pollDumpFile = () => {
          fs.readdir(crashesDir, (err, files) => {
            if (err) return
            const dumps = files.filter((file) => /\.dmp$/.test(file) && !existingDumpFiles.has(file))
            if (!dumps.length) return

            assert.equal(1, dumps.length)
            dumpFile = path.join(crashesDir, dumps[0])
            clearInterval(pollInterval)
            // dump file should not be deleted when not uploading, so we wait
            // 1s and assert it still exists in `testDone`
            setTimeout(testDone, 1000)
          })
        }

        remote.ipcMain.once('list-existing-dumps', (event) => {
          fs.readdir(crashesDir, (err, files) => {
            if (!err) {
              for (const file of files) {
                if (/\.dmp$/.test(file)) {
                  existingDumpFiles.add(file)
                }
              }
            }
            event.returnValue = null  // allow the renderer to crash
            pollInterval = setInterval(pollDumpFile, 100)
          })
        })

        stopServer = startServer({
          callback (port) {
            const crashUrl = url.format({
              protocol: 'file',
              pathname: path.join(fixtures, 'api', 'crash.html'),
              search: `?port=${port}&skipUpload=1`
            })
            w.loadURL(crashUrl)
          },
          processType: 'renderer',
          done: testDone.bind(null, true)
        })
      })
      it('should send minidump with updated extra parameters', function (done) {
        // TODO(alexeykuzmin): Skip the test instead of marking it as passed.
        if (process.env.APPVEYOR === 'True') return done()
        if (process.env.TRAVIS === 'true') return done()

        this.timeout(120000)

        stopServer = startServer({
          callback (port) {
            const crashUrl = url.format({
              protocol: 'file',
              pathname: path.join(fixtures, 'api', 'crash-restart.html'),
              search: `?port=${port}`
            })
            w.loadURL(crashUrl)
          },
          processType: 'renderer',
          done: done
        })
      })
    })
  }

  generateSpecs('without sandbox', {})
  generateSpecs('with sandbox', {
    webPreferences: {
      sandbox: true,
      preload: path.join(fixtures, 'module', 'preload-sandbox.js')
    }
  })

  describe('getProductName', () => {
    it('returns the product name if one is specified', () => {
      const name = crashReporter.getProductName()
      const expectedName = (process.platform === 'darwin') ? 'Electron Test' : 'Zombies'
      assert.equal(name, expectedName)
    })
  })

  describe('getTempDirectory', () => {
    it('returns temp directory for app if one is specified', () => {
      const tempDir = crashReporter.getTempDirectory()
      assert.equal(tempDir, app.getPath('temp'))
    })
  })

  describe('start(options)', () => {
    it('requires that the companyName and submitURL options be specified', () => {
      assert.throws(() => {
        crashReporter.start({companyName: 'Missing submitURL'})
      }, /submitURL is a required option to crashReporter\.start/)
      assert.throws(() => {
        crashReporter.start({submitURL: 'Missing companyName'})
      }, /companyName is a required option to crashReporter\.start/)
    })
    it('can be called multiple times', () => {
      assert.doesNotThrow(() => {
        crashReporter.start({
          companyName: 'Umbrella Corporation',
          submitURL: 'http://127.0.0.1/crashes'
        })

        crashReporter.start({
          companyName: 'Umbrella Corporation 2',
          submitURL: 'http://127.0.0.1/more-crashes'
        })
      })
    })
  })

  describe('getCrashesDirectory', () => {
    it('correctly returns the directory', () => {
      const crashesDir = crashReporter.getCrashesDirectory()
      let dir
      if (process.platform === 'win32') {
        dir = `${app.getPath('temp')}/Zombies Crashes`
      } else {
        dir = `${app.getPath('temp')}/Electron Test Crashes`
      }
      assert.equal(crashesDir, dir)
    })
  })

  describe('getUploadedReports', () => {
    it('returns an array of reports', () => {
      const reports = crashReporter.getUploadedReports()
      assert(typeof reports === 'object')
    })
  })

  describe('getLastCrashReport', () => {
    it('correctly returns the most recent report', () => {
      if (process.env.TRAVIS === 'False') {
        const reports = crashReporter.getUploadedReports()
        const lastReport = reports[0]
        assert(lastReport != null)
      }
    })
  })

  describe('getUploadToServer()', () => {
    it('throws an error when called from the renderer process', () => {
      assert.throws(() => require('electron').crashReporter.getUploadToServer())
    })
    it('returns true when uploadToServer is set to true', function () {
      if (process.platform !== 'darwin') {
        // FIXME(alexeykuzmin): Skip the test.
        // this.skip()
        return
      }

      crashReporter.start({
        companyName: 'Umbrella Corporation',
        submitURL: 'http://127.0.0.1/crashes',
        uploadToServer: true
      })
      assert.equal(crashReporter.getUploadToServer(), true)
    })
    it('returns false when uploadToServer is set to false', function () {
      if (process.platform !== 'darwin') {
        // FIXME(alexeykuzmin): Skip the test.
        // this.skip()
        return
      }

      crashReporter.start({
        companyName: 'Umbrella Corporation',
        submitURL: 'http://127.0.0.1/crashes',
        uploadToServer: true
      })
      crashReporter.setUploadToServer(false)
      assert.equal(crashReporter.getUploadToServer(), false)
    })
  })

  describe('setUploadToServer(uploadToServer)', () => {
    it('throws an error when called from the renderer process', () => {
      assert.throws(() => require('electron').crashReporter.setUploadToServer('arg'))
    })
    it('sets uploadToServer false when called with false', function () {
      if (process.platform !== 'darwin') {
        // FIXME(alexeykuzmin): Skip the test.
        // this.skip()
        return
      }

      crashReporter.start({
        companyName: 'Umbrella Corporation',
        submitURL: 'http://127.0.0.1/crashes',
        uploadToServer: true
      })
      crashReporter.setUploadToServer(false)
      assert.equal(crashReporter.getUploadToServer(), false)
    })
    it('sets uploadToServer true when called with true', function () {
      if (process.platform !== 'darwin') {
        // FIXME(alexeykuzmin): Skip the test.
        // this.skip()
        return
      }

      crashReporter.start({
        companyName: 'Umbrella Corporation',
        submitURL: 'http://127.0.0.1/crashes',
        uploadToServer: false
      })
      crashReporter.setUploadToServer(true)
      assert.equal(crashReporter.getUploadToServer(), true)
    })
  })

  describe('Parameters', () => {
    it('returns all of the current parameters', () => {
      crashReporter.start({
        companyName: 'Umbrella Corporation',
        submitURL: 'http://127.0.0.1/crashes'
      })

      const parameters = crashReporter.getParameters()
      assert(typeof parameters === 'object')
    })
    it('adds a parameter to current parameters', function () {
      if (process.platform !== 'darwin') {
        // FIXME(alexeykuzmin): Skip the test.
        // this.skip()
        return
      }

      crashReporter.start({
        companyName: 'Umbrella Corporation',
        submitURL: 'http://127.0.0.1/crashes'
      })

      crashReporter.addExtraParameter('hello', 'world')
      assert('hello' in crashReporter.getParameters())
    })
    it('removes a parameter from current parameters', function () {
      if (process.platform !== 'darwin') {
        // FIXME(alexeykuzmin): Skip the test.
        // this.skip()
        return
      }

      crashReporter.start({
        companyName: 'Umbrella Corporation',
        submitURL: 'http://127.0.0.1/crashes'
      })

      crashReporter.addExtraParameter('hello', 'world')
      assert('hello' in crashReporter.getParameters())

      crashReporter.removeExtraParameter('hello')
      assert(!('hello' in crashReporter.getParameters()))
    })
  })
})

const waitForCrashReport = () => {
  return new Promise((resolve, reject) => {
    let times = 0
    const checkForReport = () => {
      if (crashReporter.getLastCrashReport() != null) {
        resolve()
      } else if (times >= 10) {
        reject(new Error('No crash report available'))
      } else {
        times++
        setTimeout(checkForReport, 100)
      }
    }
    checkForReport()
  })
}

const startServer = ({callback, processType, done}) => {
  let called = false
  let server = http.createServer((req, res) => {
    const form = new multiparty.Form()
    form.parse(req, (error, fields) => {
      if (error) throw error
      if (called) return
      called = true
      assert.equal(fields.prod, 'Electron')
      assert.equal(fields.ver, process.versions.electron)
      assert.equal(fields.process_type, processType)
      assert.equal(fields.platform, process.platform)
      assert.equal(fields.extra1, 'extra1')
      assert.equal(fields.extra2, 'extra2')
      assert.equal(fields.extra3, undefined)
      assert.equal(fields._productName, 'Zombies')
      assert.equal(fields._companyName, 'Umbrella Corporation')
      assert.equal(fields._version, app.getVersion())

      const reportId = 'abc-123-def-456-abc-789-abc-123-abcd'
      res.end(reportId, () => {
        waitForCrashReport().then(() => {
          assert.equal(crashReporter.getLastCrashReport().id, reportId)
          assert.notEqual(crashReporter.getUploadedReports().length, 0)
          assert.equal(crashReporter.getUploadedReports()[0].id, reportId)
          req.socket.destroy()
          done()
        }, done)
      })
    })
  })

  const activeConnections = new Set()
  server.on('connection', (connection) => {
    activeConnections.add(connection)
    connection.once('close', () => {
      activeConnections.delete(connection)
    })
  })

  let {port} = remote.process
  server.listen(port, '127.0.0.1', () => {
    port = server.address().port
    remote.process.port = port
    if (process.platform === 'darwin') {
      crashReporter.start({
        companyName: 'Umbrella Corporation',
        submitURL: 'http://127.0.0.1:' + port
      })
    }
    callback(port)
  })

  return function stopServer (done) {
    for (const connection of activeConnections) {
      connection.destroy()
    }
    server.close(() => {
      done()
    })
  }
}

const stopCrashService = () => {
  const {crashServicePid} = remote.process
  if (crashServicePid) {
    remote.process.crashServicePid = 0
    try {
      process.kill(crashServicePid)
    } catch (error) {
      if (error.code !== 'ESRCH') {
        throw error
      }
    }
  }
}
<commit_msg>Disable a couple of crashReporter tests<commit_after>const assert = require('assert')
const childProcess = require('child_process')
const fs = require('fs')
const http = require('http')
const multiparty = require('multiparty')
const path = require('path')
const temp = require('temp').track()
const url = require('url')
const {closeWindow} = require('./window-helpers')

const {remote} = require('electron')
const {app, BrowserWindow, crashReporter} = remote.require('electron')

describe('crashReporter module', () => {
  if (process.mas || process.env.DISABLE_CRASH_REPORTER_TESTS) return

  let originalTempDirectory = null
  let tempDirectory = null

  before(() => {
    tempDirectory = temp.mkdirSync('electronCrashReporterSpec-')
    originalTempDirectory = app.getPath('temp')
    app.setPath('temp', tempDirectory)
  })

  after(() => {
    app.setPath('temp', originalTempDirectory)
  })

  const fixtures = path.resolve(__dirname, 'fixtures')
  const generateSpecs = (description, browserWindowOpts) => {
    describe(description, () => {
      let w = null
      let stopServer = null

      beforeEach(() => {
        stopServer = null
        w = new BrowserWindow(Object.assign({ show: false }, browserWindowOpts))
      })

      afterEach(() => closeWindow(w).then(() => { w = null }))

      afterEach(() => {
        stopCrashService()
      })

      afterEach((done) => {
        if (stopServer != null) {
          stopServer(done)
        } else {
          done()
        }
      })

      // TODO(alexeykuzmin): Disabled during Chromium 61 upgrade.
      // It times out on Linux. Fix it and enable.
      xit('should send minidump when renderer crashes', function (done) {
        // TODO(alexeykuzmin): Skip the test instead of marking it as passed.
        if (process.env.APPVEYOR === 'True') return done()
        if (process.env.TRAVIS === 'true') return done()

        this.timeout(120000)

        stopServer = startServer({
          callback (port) {
            const crashUrl = url.format({
              protocol: 'file',
              pathname: path.join(fixtures, 'api', 'crash.html'),
              search: '?port=' + port
            })
            w.loadURL(crashUrl)
          },
          processType: 'renderer',
          done: done
        })
      })

      // TODO(alexeykuzmin): Disabled during Chromium 61 upgrade.
      // It times out on Linux. Fix it and enable.
      xit('should send minidump when node processes crash', function (done) {
        // TODO(alexeykuzmin): Skip the test instead of marking it as passed.
        if (process.env.APPVEYOR === 'True') return done()
        if (process.env.TRAVIS === 'true') return done()

        this.timeout(120000)

        stopServer = startServer({
          callback (port) {
            const crashesDir = path.join(app.getPath('temp'), `${process.platform === 'win32' ? 'Zombies' : app.getName()} Crashes`)
            const version = app.getVersion()
            const crashPath = path.join(fixtures, 'module', 'crash.js')

            if (process.platform === 'win32') {
              const crashServiceProcess = childProcess.spawn(process.execPath, [
                `--reporter-url=http://127.0.0.1:${port}`,
                '--application-name=Zombies',
                `--crashes-directory=${crashesDir}`
              ], {
                env: {
                  ELECTRON_INTERNAL_CRASH_SERVICE: 1
                },
                detached: true
              })
              remote.process.crashServicePid = crashServiceProcess.pid
            }

            childProcess.fork(crashPath, [port, version, crashesDir], {silent: true})
          },
          processType: 'browser',
          done: done
        })
      })

      // TODO(alexeykuzmin): Disabled during Chromium 61 upgrade.
      // Fix it and enable.
      xit('should not send minidump if uploadToServer is false', function (done) {
        this.timeout(120000)

        let dumpFile
        let crashesDir = crashReporter.getCrashesDirectory()
        const existingDumpFiles = new Set()
        if (process.platform === 'darwin') {
          // crashpad puts the dump files in the "completed" subdirectory
          crashesDir = path.join(crashesDir, 'completed')
          crashReporter.setUploadToServer(false)
        }
        const testDone = (uploaded) => {
          if (uploaded) return done(new Error('Uploaded crash report'))
          if (process.platform === 'darwin') crashReporter.setUploadToServer(true)
          assert(fs.existsSync(dumpFile))
          done()
        }

        let pollInterval
        const pollDumpFile = () => {
          fs.readdir(crashesDir, (err, files) => {
            if (err) return
            const dumps = files.filter((file) => /\.dmp$/.test(file) && !existingDumpFiles.has(file))
            if (!dumps.length) return

            assert.equal(1, dumps.length)
            dumpFile = path.join(crashesDir, dumps[0])
            clearInterval(pollInterval)
            // dump file should not be deleted when not uploading, so we wait
            // 1s and assert it still exists in `testDone`
            setTimeout(testDone, 1000)
          })
        }

        remote.ipcMain.once('list-existing-dumps', (event) => {
          fs.readdir(crashesDir, (err, files) => {
            if (!err) {
              for (const file of files) {
                if (/\.dmp$/.test(file)) {
                  existingDumpFiles.add(file)
                }
              }
            }
            event.returnValue = null  // allow the renderer to crash
            pollInterval = setInterval(pollDumpFile, 100)
          })
        })

        stopServer = startServer({
          callback (port) {
            const crashUrl = url.format({
              protocol: 'file',
              pathname: path.join(fixtures, 'api', 'crash.html'),
              search: `?port=${port}&skipUpload=1`
            })
            w.loadURL(crashUrl)
          },
          processType: 'renderer',
          done: testDone.bind(null, true)
        })
      })

      // TODO(alexeykuzmin): Disabled during Chromium 61 upgrade.
      // It times out on Linux. Fix it and enable.
      xit('should send minidump with updated extra parameters', function (done) {
        // TODO(alexeykuzmin): Skip the test instead of marking it as passed.
        if (process.env.APPVEYOR === 'True') return done()
        if (process.env.TRAVIS === 'true') return done()

        this.timeout(120000)

        stopServer = startServer({
          callback (port) {
            const crashUrl = url.format({
              protocol: 'file',
              pathname: path.join(fixtures, 'api', 'crash-restart.html'),
              search: `?port=${port}`
            })
            w.loadURL(crashUrl)
          },
          processType: 'renderer',
          done: done
        })
      })
    })
  }

  generateSpecs('without sandbox', {})
  generateSpecs('with sandbox', {
    webPreferences: {
      sandbox: true,
      preload: path.join(fixtures, 'module', 'preload-sandbox.js')
    }
  })

  describe('getProductName', () => {
    it('returns the product name if one is specified', () => {
      const name = crashReporter.getProductName()
      const expectedName = (process.platform === 'darwin') ? 'Electron Test' : 'Zombies'
      assert.equal(name, expectedName)
    })
  })

  describe('getTempDirectory', () => {
    it('returns temp directory for app if one is specified', () => {
      const tempDir = crashReporter.getTempDirectory()
      assert.equal(tempDir, app.getPath('temp'))
    })
  })

  describe('start(options)', () => {
    it('requires that the companyName and submitURL options be specified', () => {
      assert.throws(() => {
        crashReporter.start({companyName: 'Missing submitURL'})
      }, /submitURL is a required option to crashReporter\.start/)
      assert.throws(() => {
        crashReporter.start({submitURL: 'Missing companyName'})
      }, /companyName is a required option to crashReporter\.start/)
    })
    it('can be called multiple times', () => {
      assert.doesNotThrow(() => {
        crashReporter.start({
          companyName: 'Umbrella Corporation',
          submitURL: 'http://127.0.0.1/crashes'
        })

        crashReporter.start({
          companyName: 'Umbrella Corporation 2',
          submitURL: 'http://127.0.0.1/more-crashes'
        })
      })
    })
  })

  describe('getCrashesDirectory', () => {
    it('correctly returns the directory', () => {
      const crashesDir = crashReporter.getCrashesDirectory()
      let dir
      if (process.platform === 'win32') {
        dir = `${app.getPath('temp')}/Zombies Crashes`
      } else {
        dir = `${app.getPath('temp')}/Electron Test Crashes`
      }
      assert.equal(crashesDir, dir)
    })
  })

  describe('getUploadedReports', () => {
    it('returns an array of reports', () => {
      const reports = crashReporter.getUploadedReports()
      assert(typeof reports === 'object')
    })
  })

  describe('getLastCrashReport', () => {
    it('correctly returns the most recent report', () => {
      if (process.env.TRAVIS === 'False') {
        const reports = crashReporter.getUploadedReports()
        const lastReport = reports[0]
        assert(lastReport != null)
      }
    })
  })

  describe('getUploadToServer()', () => {
    it('throws an error when called from the renderer process', () => {
      assert.throws(() => require('electron').crashReporter.getUploadToServer())
    })
    it('returns true when uploadToServer is set to true', function () {
      if (process.platform !== 'darwin') {
        // FIXME(alexeykuzmin): Skip the test.
        // this.skip()
        return
      }

      crashReporter.start({
        companyName: 'Umbrella Corporation',
        submitURL: 'http://127.0.0.1/crashes',
        uploadToServer: true
      })
      assert.equal(crashReporter.getUploadToServer(), true)
    })
    it('returns false when uploadToServer is set to false', function () {
      if (process.platform !== 'darwin') {
        // FIXME(alexeykuzmin): Skip the test.
        // this.skip()
        return
      }

      crashReporter.start({
        companyName: 'Umbrella Corporation',
        submitURL: 'http://127.0.0.1/crashes',
        uploadToServer: true
      })
      crashReporter.setUploadToServer(false)
      assert.equal(crashReporter.getUploadToServer(), false)
    })
  })

  describe('setUploadToServer(uploadToServer)', () => {
    it('throws an error when called from the renderer process', () => {
      assert.throws(() => require('electron').crashReporter.setUploadToServer('arg'))
    })
    it('sets uploadToServer false when called with false', function () {
      if (process.platform !== 'darwin') {
        // FIXME(alexeykuzmin): Skip the test.
        // this.skip()
        return
      }

      crashReporter.start({
        companyName: 'Umbrella Corporation',
        submitURL: 'http://127.0.0.1/crashes',
        uploadToServer: true
      })
      crashReporter.setUploadToServer(false)
      assert.equal(crashReporter.getUploadToServer(), false)
    })
    it('sets uploadToServer true when called with true', function () {
      if (process.platform !== 'darwin') {
        // FIXME(alexeykuzmin): Skip the test.
        // this.skip()
        return
      }

      crashReporter.start({
        companyName: 'Umbrella Corporation',
        submitURL: 'http://127.0.0.1/crashes',
        uploadToServer: false
      })
      crashReporter.setUploadToServer(true)
      assert.equal(crashReporter.getUploadToServer(), true)
    })
  })

  describe('Parameters', () => {
    it('returns all of the current parameters', () => {
      crashReporter.start({
        companyName: 'Umbrella Corporation',
        submitURL: 'http://127.0.0.1/crashes'
      })

      const parameters = crashReporter.getParameters()
      assert(typeof parameters === 'object')
    })
    it('adds a parameter to current parameters', function () {
      if (process.platform !== 'darwin') {
        // FIXME(alexeykuzmin): Skip the test.
        // this.skip()
        return
      }

      crashReporter.start({
        companyName: 'Umbrella Corporation',
        submitURL: 'http://127.0.0.1/crashes'
      })

      crashReporter.addExtraParameter('hello', 'world')
      assert('hello' in crashReporter.getParameters())
    })
    it('removes a parameter from current parameters', function () {
      if (process.platform !== 'darwin') {
        // FIXME(alexeykuzmin): Skip the test.
        // this.skip()
        return
      }

      crashReporter.start({
        companyName: 'Umbrella Corporation',
        submitURL: 'http://127.0.0.1/crashes'
      })

      crashReporter.addExtraParameter('hello', 'world')
      assert('hello' in crashReporter.getParameters())

      crashReporter.removeExtraParameter('hello')
      assert(!('hello' in crashReporter.getParameters()))
    })
  })
})

const waitForCrashReport = () => {
  return new Promise((resolve, reject) => {
    let times = 0
    const checkForReport = () => {
      if (crashReporter.getLastCrashReport() != null) {
        resolve()
      } else if (times >= 10) {
        reject(new Error('No crash report available'))
      } else {
        times++
        setTimeout(checkForReport, 100)
      }
    }
    checkForReport()
  })
}

const startServer = ({callback, processType, done}) => {
  let called = false
  let server = http.createServer((req, res) => {
    const form = new multiparty.Form()
    form.parse(req, (error, fields) => {
      if (error) throw error
      if (called) return
      called = true
      assert.equal(fields.prod, 'Electron')
      assert.equal(fields.ver, process.versions.electron)
      assert.equal(fields.process_type, processType)
      assert.equal(fields.platform, process.platform)
      assert.equal(fields.extra1, 'extra1')
      assert.equal(fields.extra2, 'extra2')
      assert.equal(fields.extra3, undefined)
      assert.equal(fields._productName, 'Zombies')
      assert.equal(fields._companyName, 'Umbrella Corporation')
      assert.equal(fields._version, app.getVersion())

      const reportId = 'abc-123-def-456-abc-789-abc-123-abcd'
      res.end(reportId, () => {
        waitForCrashReport().then(() => {
          assert.equal(crashReporter.getLastCrashReport().id, reportId)
          assert.notEqual(crashReporter.getUploadedReports().length, 0)
          assert.equal(crashReporter.getUploadedReports()[0].id, reportId)
          req.socket.destroy()
          done()
        }, done)
      })
    })
  })

  const activeConnections = new Set()
  server.on('connection', (connection) => {
    activeConnections.add(connection)
    connection.once('close', () => {
      activeConnections.delete(connection)
    })
  })

  let {port} = remote.process
  server.listen(port, '127.0.0.1', () => {
    port = server.address().port
    remote.process.port = port
    if (process.platform === 'darwin') {
      crashReporter.start({
        companyName: 'Umbrella Corporation',
        submitURL: 'http://127.0.0.1:' + port
      })
    }
    callback(port)
  })

  return function stopServer (done) {
    for (const connection of activeConnections) {
      connection.destroy()
    }
    server.close(() => {
      done()
    })
  }
}

const stopCrashService = () => {
  const {crashServicePid} = remote.process
  if (crashServicePid) {
    remote.process.crashServicePid = 0
    try {
      process.kill(crashServicePid)
    } catch (error) {
      if (error.code !== 'ESRCH') {
        throw error
      }
    }
  }
}
<|endoftext|>


<commit_before>/*global define*/

define(function () {
    'use strict';

    var ListController = function ($scope, $stateParams, $location, $anchorScroll, ReadQueries, progression, view, dataStore, totalItems) {
        this.$scope = $scope;
        this.$stateParams = $stateParams;
        this.$location = $location;
        this.$anchorScroll = $anchorScroll;
        this.ReadQueries = ReadQueries;
        this.progression = progression;
        this.view = view;
        this.entity = view.getEntity();
        this.title = view.title();
        this.description = view.description();
        this.actions = view.actions();
        this.batchActions = view.batchActions();
        this.loadingPage = false;
        this.search = $stateParams.search;
        this.filters = view.filters();
        this.hasFilters = Object.keys(this.filters).length > 0;
        this.dataStore = dataStore;
        this.fields = view.fields();
        this.listActions = view.listActions();
        this.totalItems = totalItems;
        this.page = $stateParams.page || 1;
        this.infinitePagination = this.view.infinitePagination();
        this.nextPageCallback = this.nextPage.bind(this);
        this.setPageCallback = this.setPage.bind(this);
        this.selection = this.batchActions.length ? [] : null;
        this.sortField = this.$stateParams.sortField || this.view.getSortFieldName();
        this.sortDir = this.$stateParams.sortDir || this.view.sortDir();

        $scope.$on('$destroy', this.destroy.bind(this));
    };

    ListController.prototype.nextPage = function (page) {
        if (this.loadingPage) {
            return;
        }

        var progression = this.progression,
            self = this;

        progression.start();

        this.ReadQueries
            .getAll(this.view, page, true, this.search, this.sortField, this.sortDir)
            .then(function (response) {
                progression.done();
                self.dataStore.mapEntries(
                    self.entity.name(),
                    self.view.identifier(),
                    self.fields,
                    response.data
                ).map(function (entry) {
                    self.dataStore.addEntry(self.entity.uniqueId, entry);
                });
                self.loadingPage = false;
            });
    };

    ListController.prototype.setPage = function (number) {
        this.$location.search('page', number);
        this.$anchorScroll(0);
    };

    ListController.prototype.destroy = function () {
        this.$scope = undefined;
        this.$stateParams = undefined;
        this.$location = undefined;
        this.$anchorScroll = undefined;
        this.dataStore = undefined;
    };

    ListController.$inject = ['$scope', '$stateParams', '$location', '$anchorScroll', 'ReadQueries', 'progression', 'view', 'dataStore', 'totalItems'];

    return ListController;
});
<commit_msg>code review<commit_after>/*global define*/

define(function () {
    'use strict';

    var ListController = function ($scope, $stateParams, $location, $anchorScroll, ReadQueries, progression, view, dataStore, totalItems) {
        this.$scope = $scope;
        this.$stateParams = $stateParams;
        this.$location = $location;
        this.$anchorScroll = $anchorScroll;
        this.ReadQueries = ReadQueries;
        this.progression = progression;
        this.view = view;
        this.entity = view.getEntity();
        this.title = view.title();
        this.description = view.description();
        this.actions = view.actions();
        this.batchActions = view.batchActions();
        this.loadingPage = false;
        this.search = $stateParams.search;
        this.filters = view.filters();
        this.hasFilters = Object.keys(this.filters).length > 0;
        this.dataStore = dataStore;
        this.fields = view.fields();
        this.listActions = view.listActions();
        this.totalItems = totalItems;
        this.page = $stateParams.page || 1;
        this.infinitePagination = this.view.infinitePagination();
        this.nextPageCallback = this.nextPage.bind(this);
        this.setPageCallback = this.setPage.bind(this);
        this.selection = this.batchActions.length ? [] : null;
        this.sortField = this.$stateParams.sortField || this.view.getSortFieldName();
        this.sortDir = this.$stateParams.sortDir || this.view.sortDir();

        $scope.$on('$destroy', this.destroy.bind(this));
    };

    ListController.prototype.nextPage = function (page) {
        if (this.loadingPage) {
            return;
        }

        var progression = this.progression,
            self = this;

        progression.start();

        this.ReadQueries
            .getAll(this.view, page, this.search, this.sortField, this.sortDir)
            .then(function (response) {
                progression.done();
                var references = self.view.getReferences();

                self.dataStore.mapEntries(
                    self.entity.name(),
                    self.view.identifier(),
                    self.fields,
                    response.data
                ).map(function (entry) {
                    self.dataStore.fillReferencesValuesFromEntry(entry, references, true);
                    self.dataStore.addEntry(self.entity.uniqueId, entry);
                });

                self.loadingPage = false;
            });
    };

    ListController.prototype.setPage = function (number) {
        this.$location.search('page', number);
        this.$anchorScroll(0);
    };

    ListController.prototype.destroy = function () {
        this.$scope = undefined;
        this.$stateParams = undefined;
        this.$location = undefined;
        this.$anchorScroll = undefined;
        this.dataStore = undefined;
    };

    ListController.$inject = ['$scope', '$stateParams', '$location', '$anchorScroll', 'ReadQueries', 'progression', 'view', 'dataStore', 'totalItems'];

    return ListController;
});
<|endoftext|>