<commit_before>/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */

'use strict';

var EXPORTED_SYMBOLS = ['FlashStreamConverter1', 'FlashStreamConverter2'];

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;

// True only if this is the version of pdf.js that is included with firefox.
const SHUMWAY_CONTENT_TYPE = 'application/x-shockwave-flash';
const EXPECTED_PLAYPREVIEW_URI_PREFIX = 'data:application/x-moz-playpreview;,' +
                                        SHUMWAY_CONTENT_TYPE;

const FIREFOX_ID = '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}';
const SEAMONKEY_ID = '{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}';

Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import('resource://gre/modules/Services.jsm');
Cu.import('resource://gre/modules/NetUtil.jsm');

let appInfo = Cc['@mozilla.org/xre/app-info;1']
                  .getService(Ci.nsIXULAppInfo);
let Svc = {};
XPCOMUtils.defineLazyServiceGetter(Svc, 'mime',
                                   '@mozilla.org/mime;1',
                                   'nsIMIMEService');

function getBoolPref(pref, def) {
  try {
    return Services.prefs.getBoolPref(pref);
  } catch (ex) {
    return def;
  }
}

function log(aMsg) {
  let msg = 'FlashStreamConverter.js: ' + (aMsg.join ? aMsg.join('') : aMsg);
  Services.console.logStringMessage(msg);
  dump(msg + '\n');
}

function getDOMWindow(aChannel) {
  var requestor = aChannel.notificationCallbacks;
  var win = requestor.getInterface(Components.interfaces.nsIDOMWindow);
  return win;
}

// Combines two URLs. The baseUrl shall be absolute URL. If the url is an
// absolute URL, it will be returned as is.
function combineUrl(baseUrl, url) {
  if (url.indexOf(':') >= 0)
    return url;
  if (url.charAt(0) == '/') {
    // absolute path
    var i = baseUrl.indexOf('://');
    i = baseUrl.indexOf('/', i + 3);
    return baseUrl.substring(0, i) + url;
  } else {
    // relative path
    var pathLength = baseUrl.length, i;
    i = baseUrl.lastIndexOf('#');
    pathLength = i >= 0 ? i : pathLength;
    i = baseUrl.lastIndexOf('?', pathLength);
    pathLength = i >= 0 ? i : pathLength;
    var prefixLength = baseUrl.lastIndexOf('/', pathLength);
    return baseUrl.substring(0, prefixLength + 1) + url;
  }
}

function parseQueryString(qs) {
  if (!qs)
    return {};

  if (qs.charAt(0) == '?')
    qs = qs.slice(1);

  var values = qs.split('&');
  var obj = {};
  for (var i = 0; i < values.length; i++) {
    var kv = values[i].split('=');
    var key = kv[0], value = kv[1];
    obj[key] = value;
  }

  return obj;
}

// All the priviledged actions.
function ChromeActions(url, params, referer, window) {
  this.url = url;
  this.params = params;
  this.referer = referer;
  this.isOverlay = false;
  this.isPausedAtStart = false;
  this.window = window;
}

ChromeActions.prototype = {
  getBoolPref: function (data) {
    return getBoolPref(data.pref, data.def);
  },
  getPluginParams: function getPluginParams() {
    return JSON.stringify({
      url: this.url,
      params: this.params,
      isOverlay: this.isOverlay,
      isPausedAtStart: this.isPausedAtStart
     });
  },
  _canDownloadFile: function canDownloadFile(url, checkPolicyFile) {
    // TODO flash cross-origin request
    if (url === this.url)
      return true; // allow downloading for the original file

    // let's allow downloading from http(s) and same origin
    var basePrefix = /^(https?:\/\/[A-Za-z0-9\-_\.:\[\]]+\/)/i.exec(this.url);
    if (basePrefix) {
      var urlPrefix = /^(https?:\/\/[A-Za-z0-9\-_\.:\[\]]+\/)/i.exec(url);
      if (urlPrefix && basePrefix[1] === urlPrefix[1])
        return true;
    }

    return false;
  },
  loadFile: function loadFile(data) {
    var url = data.url;
    var checkPolicyFile = data.checkPolicyFile;
    var sessionId = data.sessionId;
    var limit = data.limit || 0;
    var method = data.method || "GET";
    var postData = data.postData || null;

    if (!this._canDownloadFile(url, checkPolicyFile)) {
      log("bad url " + url + " " + this.url);
      win.postMessage({callback:"loadFile", sessionId: sessionId, topic: "error",
        error: "only original swf file or file from the same origin loading supported"}, "*");
      return;
    }

    var win = this.window;

    var xhr = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
                         .createInstance(Ci.nsIXMLHttpRequest);
    xhr.open(method, url, true);
    // arraybuffer is not provide onprogress, fetching as regular chars
    if ('overrideMimeType' in xhr)
      xhr.overrideMimeType('text/plain; charset=x-user-defined');

    if (this.referer) {
      // Setting the referer uri, some site doing checks if swf is embedded
      // on the original page.
      xhr.setRequestHeader("Referer", this.referer);
    }

    // TODO apply range request headers if limit is specified

    var lastPosition = 0;
    xhr.onprogress = function (e) {
      var position = e.loaded;
      var chunk = xhr.responseText.substring(lastPosition, position);
      var data = new Uint8Array(chunk.length);
      for (var i = 0; i < data.length; i++)
        data[i] = chunk.charCodeAt(i) & 0xFF;
      win.postMessage({callback:"loadFile", sessionId: sessionId, topic: "progress",
                       array: data, loaded: e.loaded, total: e.total}, "*");
      lastPosition = position;
      if (limit && e.total >= limit) {
        xhr.abort();
      }
    };
    xhr.onreadystatechange = function(event) {
      if (xhr.readyState === 4) {
        if (xhr.status !== 200 && xhr.status !== 0) {
          win.postMessage({callback:"loadFile", sessionId: sessionId, topic: "error",
                           error: xhr.statusText}, "*");
        }
        win.postMessage({callback:"loadFile", sessionId: sessionId, topic: "close"}, "*");
      }
    }
    xhr.send(postData);
    win.postMessage({callback:"loadFile", sessionId: sessionId, topic: "open"}, "*");
  },
  fallback: function() {
    var obj = this.window.frameElement;
    var doc = obj.ownerDocument;
    var e = doc.createEvent("CustomEvent");
    e.initCustomEvent("MozPlayPlugin", true, true, null);
    obj.dispatchEvent(e);
  }
};

// Event listener to trigger chrome privedged code.
function RequestListener(actions) {
  this.actions = actions;
}
// Receive an event and synchronously or asynchronously responds.
RequestListener.prototype.receive = function(event) {
  var message = event.target;
  var doc = message.ownerDocument;
  var action = event.detail.action;
  var data = event.detail.data;
  var sync = event.detail.sync;
  var actions = this.actions;
  if (!(action in actions)) {
    log('Unknown action: ' + action);
    return;
  }
  if (sync) {
    var response = actions[action].call(this.actions, data);
    var detail = event.detail;
    detail.__exposedProps__ = {response: 'r'};
    detail.response = response;
  } else {
    var response;
    if (!event.detail.callback) {
      doc.documentElement.removeChild(message);
      response = null;
    } else {
      response = function sendResponse(response) {
        try {
          var listener = doc.createEvent('CustomEvent');
          listener.initCustomEvent('shumway.response', true, false,
                                   {response: response,
                                    __exposedProps__: {response: 'r'}});

          return message.dispatchEvent(listener);
        } catch (e) {
          // doc is no longer accessible because the requestor is already
          // gone. unloaded content cannot receive the response anyway.
        }
      };
    }
    actions[action].call(this.actions, data, response);
  }
};

function FlashStreamConverterBase() {
}

FlashStreamConverterBase.prototype = {
  QueryInterface: XPCOMUtils.generateQI([
      Ci.nsISupports,
      Ci.nsIStreamConverter,
      Ci.nsIStreamListener,
      Ci.nsIRequestObserver
  ]),

  /*
   * This component works as such:
   * 1. asyncConvertData stores the listener
   * 2. onStartRequest creates a new channel, streams the viewer and cancels
   *    the request so pdf.js can do the request
   * Since the request is cancelled onDataAvailable should not be called. The
   * onStopRequest does nothing. The convert function just returns the stream,
   * it's just the synchronous version of asyncConvertData.
   */

  // nsIStreamConverter::convert
  convert: function(aFromStream, aFromType, aToType, aCtxt) {
    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  },

  isValidRequest: function() {
    return true;
  },

  createChromeActions: function(window, urlHint) {
    var url;
    var element = window.frameElement;
    var isOverlay = false;
    var params = {};
    if (element) {
      var tagName = element.nodeName;
      while (tagName != 'EMBED' && tagName != 'OBJECT') {
        // plugin overlay skipping until the target plugin is found
        isOverlay = true;
        element = element.parentNode;
        if (!element)
          throw 'Plugin element is not found';
        tagName = element.nodeName;
      }
      if (tagName == 'EMBED') {
        params = parseQueryString(element.getAttribute('flashvars'));
        url = element.getAttribute('src');
      } else {
        for (var i = 0; i < element.childNodes.length; ++i) {
          var paramElement = element.childNodes[i];
          if (paramElement.nodeType != 1 ||
              paramElement.nodeName != 'PARAM') continue;

          params[paramElement.getAttribute('name')] = paramElement.getAttribute('value');
        }
        var dataAttribute = element.getAttribute('data');
        url = dataAttribute || params.movie || params.src;
      }
    }
    var element = window.frameElement;
    var baseUrl = element ? element.ownerDocument.location.href : null; // XXX base url?

    url = url ? combineUrl(baseUrl, url) : urlHint;

    var actions = new ChromeActions(url, params, baseUrl, window);
    actions.isOverlay = isOverlay;
    actions.isPausedAtStart = /\bpaused=true$/.test(urlHint);
    return actions;
  },

  // nsIStreamConverter::asyncConvertData
  asyncConvertData: function(aFromType, aToType, aListener, aCtxt) {
    if(!this.isValidRequest(aCtxt))
      throw Cr.NS_ERROR_NOT_IMPLEMENTED;

    // Store the listener passed to us
    this.listener = aListener;
  },

  // nsIStreamListener::onDataAvailable
  onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, aCount) {
    // Do nothing since all the data loading is handled by the viewer.
    log('SANITY CHECK: onDataAvailable SHOULD NOT BE CALLED!');
  },

  // nsIRequestObserver::onStartRequest
  onStartRequest: function(aRequest, aContext) {

    // Setup the request so we can use it below.
    aRequest.QueryInterface(Ci.nsIChannel);
    // Cancel the request so the viewer can handle it.
    aRequest.cancel(Cr.NS_BINDING_ABORTED);

    var originalURI = aRequest.URI;

    // checking if the plug-in shall be run in simple mode
    var isSimpleMode = originalURI.spec === EXPECTED_PLAYPREVIEW_URI_PREFIX &&
      getBoolPref('shumway.simpleMode', false);

    // Create a new channel that is viewer loaded as a resource.
    var ioService = Services.io;
    var channel = ioService.newChannel(isSimpleMode ?
                    'resource://shumway/web/simple.html' :
                    'resource://shumway/web/viewer.html', null, null);

    var converter = this;
    var listener = this.listener;
    // Proxy all the request observer calls, when it gets to onStopRequest
    // we can get the dom window.
    var proxy = {
      onStartRequest: function() {
        listener.onStartRequest.apply(listener, arguments);
      },
      onDataAvailable: function() {
        listener.onDataAvailable.apply(listener, arguments);
      },
      onStopRequest: function() {
        var domWindow = getDOMWindow(channel);
        // Double check the url is still the correct one.
        if (domWindow.document.documentURIObject.equals(channel.originalURI)) {
          let actions = converter.createChromeActions(domWindow,
                                                      originalURI.spec);
          let requestListener = new RequestListener(actions);
          domWindow.addEventListener('shumway.message', function(event) {
            requestListener.receive(event);
          }, false, true);
        }
        listener.onStopRequest.apply(listener, arguments);
      }
    };

    // XXX? Keep the URL the same so the browser sees it as the same.
    // channel.originalURI = aRequest.URI;
    channel.asyncOpen(proxy, aContext);
  },

  // nsIRequestObserver::onStopRequest
  onStopRequest: function(aRequest, aContext, aStatusCode) {
    // Do nothing.
  }
};

// properties required for XPCOM registration:
function copyProperties(obj, template) {
  for (var prop in template) {
    obj[prop] = template[prop];
  }
}

function FlashStreamConverter1() {}
FlashStreamConverter1.prototype = new FlashStreamConverterBase();
copyProperties(FlashStreamConverter1.prototype, {
  classID: Components.ID('{4c6030f7-e20a-264f-5b0e-ada3a9e97384}'),
  classDescription: 'Shumway Content Converter Component',
  contractID: '@mozilla.org/streamconv;1?from=application/x-shockwave-flash&to=*/*'
});

function FlashStreamConverter2() {}
FlashStreamConverter2.prototype = new FlashStreamConverterBase();
copyProperties(FlashStreamConverter2.prototype, {
  classID: Components.ID('{4c6030f7-e20a-264f-5f9b-ada3a9e97384}'),
  classDescription: 'Shumway PlayPreview Component',
  contractID: '@mozilla.org/streamconv;1?from=application/x-moz-playpreview&to=*/*'
});
FlashStreamConverter2.prototype.isValidRequest =
  (function(aCtxt) {
    try {
      var request = aCtxt;
      request.QueryInterface(Ci.nsIChannel);
      var spec = request.URI.spec;
      return spec.indexOf(EXPECTED_PLAYPREVIEW_URI_PREFIX) === 0;
    } catch (e) {
      return false;
    }
  });

var NSGetFactory1 = XPCOMUtils.generateNSGetFactory([FlashStreamConverter1]);
var NSGetFactory2 = XPCOMUtils.generateNSGetFactory([FlashStreamConverter2]);
<commit_msg>Whitelist pref access.<commit_after>/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */

'use strict';

var EXPORTED_SYMBOLS = ['FlashStreamConverter1', 'FlashStreamConverter2'];

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;

// True only if this is the version of pdf.js that is included with firefox.
const SHUMWAY_CONTENT_TYPE = 'application/x-shockwave-flash';
const EXPECTED_PLAYPREVIEW_URI_PREFIX = 'data:application/x-moz-playpreview;,' +
                                        SHUMWAY_CONTENT_TYPE;

const FIREFOX_ID = '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}';
const SEAMONKEY_ID = '{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}';

Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import('resource://gre/modules/Services.jsm');
Cu.import('resource://gre/modules/NetUtil.jsm');

let appInfo = Cc['@mozilla.org/xre/app-info;1']
                  .getService(Ci.nsIXULAppInfo);
let Svc = {};
XPCOMUtils.defineLazyServiceGetter(Svc, 'mime',
                                   '@mozilla.org/mime;1',
                                   'nsIMIMEService');

function getBoolPref(pref, def) {
  try {
    return Services.prefs.getBoolPref(pref);
  } catch (ex) {
    return def;
  }
}

function log(aMsg) {
  let msg = 'FlashStreamConverter.js: ' + (aMsg.join ? aMsg.join('') : aMsg);
  Services.console.logStringMessage(msg);
  dump(msg + '\n');
}

function getDOMWindow(aChannel) {
  var requestor = aChannel.notificationCallbacks;
  var win = requestor.getInterface(Components.interfaces.nsIDOMWindow);
  return win;
}

// Combines two URLs. The baseUrl shall be absolute URL. If the url is an
// absolute URL, it will be returned as is.
function combineUrl(baseUrl, url) {
  if (url.indexOf(':') >= 0)
    return url;
  if (url.charAt(0) == '/') {
    // absolute path
    var i = baseUrl.indexOf('://');
    i = baseUrl.indexOf('/', i + 3);
    return baseUrl.substring(0, i) + url;
  } else {
    // relative path
    var pathLength = baseUrl.length, i;
    i = baseUrl.lastIndexOf('#');
    pathLength = i >= 0 ? i : pathLength;
    i = baseUrl.lastIndexOf('?', pathLength);
    pathLength = i >= 0 ? i : pathLength;
    var prefixLength = baseUrl.lastIndexOf('/', pathLength);
    return baseUrl.substring(0, prefixLength + 1) + url;
  }
}

function parseQueryString(qs) {
  if (!qs)
    return {};

  if (qs.charAt(0) == '?')
    qs = qs.slice(1);

  var values = qs.split('&');
  var obj = {};
  for (var i = 0; i < values.length; i++) {
    var kv = values[i].split('=');
    var key = kv[0], value = kv[1];
    obj[key] = value;
  }

  return obj;
}

// All the priviledged actions.
function ChromeActions(url, params, referer, window) {
  this.url = url;
  this.params = params;
  this.referer = referer;
  this.isOverlay = false;
  this.isPausedAtStart = false;
  this.window = window;
}

ChromeActions.prototype = {
  getBoolPref: function (data) {
    if (!/^shumway\./.test(data.pref)) {
      return null;
    }
    return getBoolPref(data.pref, data.def);
  },
  getPluginParams: function getPluginParams() {
    return JSON.stringify({
      url: this.url,
      params: this.params,
      isOverlay: this.isOverlay,
      isPausedAtStart: this.isPausedAtStart
     });
  },
  _canDownloadFile: function canDownloadFile(url, checkPolicyFile) {
    // TODO flash cross-origin request
    if (url === this.url)
      return true; // allow downloading for the original file

    // let's allow downloading from http(s) and same origin
    var basePrefix = /^(https?:\/\/[A-Za-z0-9\-_\.:\[\]]+\/)/i.exec(this.url);
    if (basePrefix) {
      var urlPrefix = /^(https?:\/\/[A-Za-z0-9\-_\.:\[\]]+\/)/i.exec(url);
      if (urlPrefix && basePrefix[1] === urlPrefix[1])
        return true;
    }

    return false;
  },
  loadFile: function loadFile(data) {
    var url = data.url;
    var checkPolicyFile = data.checkPolicyFile;
    var sessionId = data.sessionId;
    var limit = data.limit || 0;
    var method = data.method || "GET";
    var postData = data.postData || null;

    if (!this._canDownloadFile(url, checkPolicyFile)) {
      log("bad url " + url + " " + this.url);
      win.postMessage({callback:"loadFile", sessionId: sessionId, topic: "error",
        error: "only original swf file or file from the same origin loading supported"}, "*");
      return;
    }

    var win = this.window;

    var xhr = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
                         .createInstance(Ci.nsIXMLHttpRequest);
    xhr.open(method, url, true);
    // arraybuffer is not provide onprogress, fetching as regular chars
    if ('overrideMimeType' in xhr)
      xhr.overrideMimeType('text/plain; charset=x-user-defined');

    if (this.referer) {
      // Setting the referer uri, some site doing checks if swf is embedded
      // on the original page.
      xhr.setRequestHeader("Referer", this.referer);
    }

    // TODO apply range request headers if limit is specified

    var lastPosition = 0;
    xhr.onprogress = function (e) {
      var position = e.loaded;
      var chunk = xhr.responseText.substring(lastPosition, position);
      var data = new Uint8Array(chunk.length);
      for (var i = 0; i < data.length; i++)
        data[i] = chunk.charCodeAt(i) & 0xFF;
      win.postMessage({callback:"loadFile", sessionId: sessionId, topic: "progress",
                       array: data, loaded: e.loaded, total: e.total}, "*");
      lastPosition = position;
      if (limit && e.total >= limit) {
        xhr.abort();
      }
    };
    xhr.onreadystatechange = function(event) {
      if (xhr.readyState === 4) {
        if (xhr.status !== 200 && xhr.status !== 0) {
          win.postMessage({callback:"loadFile", sessionId: sessionId, topic: "error",
                           error: xhr.statusText}, "*");
        }
        win.postMessage({callback:"loadFile", sessionId: sessionId, topic: "close"}, "*");
      }
    }
    xhr.send(postData);
    win.postMessage({callback:"loadFile", sessionId: sessionId, topic: "open"}, "*");
  },
  fallback: function() {
    var obj = this.window.frameElement;
    var doc = obj.ownerDocument;
    var e = doc.createEvent("CustomEvent");
    e.initCustomEvent("MozPlayPlugin", true, true, null);
    obj.dispatchEvent(e);
  }
};

// Event listener to trigger chrome privedged code.
function RequestListener(actions) {
  this.actions = actions;
}
// Receive an event and synchronously or asynchronously responds.
RequestListener.prototype.receive = function(event) {
  var message = event.target;
  var doc = message.ownerDocument;
  var action = event.detail.action;
  var data = event.detail.data;
  var sync = event.detail.sync;
  var actions = this.actions;
  if (!(action in actions)) {
    log('Unknown action: ' + action);
    return;
  }
  if (sync) {
    var response = actions[action].call(this.actions, data);
    var detail = event.detail;
    detail.__exposedProps__ = {response: 'r'};
    detail.response = response;
  } else {
    var response;
    if (!event.detail.callback) {
      doc.documentElement.removeChild(message);
      response = null;
    } else {
      response = function sendResponse(response) {
        try {
          var listener = doc.createEvent('CustomEvent');
          listener.initCustomEvent('shumway.response', true, false,
                                   {response: response,
                                    __exposedProps__: {response: 'r'}});

          return message.dispatchEvent(listener);
        } catch (e) {
          // doc is no longer accessible because the requestor is already
          // gone. unloaded content cannot receive the response anyway.
        }
      };
    }
    actions[action].call(this.actions, data, response);
  }
};

function FlashStreamConverterBase() {
}

FlashStreamConverterBase.prototype = {
  QueryInterface: XPCOMUtils.generateQI([
      Ci.nsISupports,
      Ci.nsIStreamConverter,
      Ci.nsIStreamListener,
      Ci.nsIRequestObserver
  ]),

  /*
   * This component works as such:
   * 1. asyncConvertData stores the listener
   * 2. onStartRequest creates a new channel, streams the viewer and cancels
   *    the request so pdf.js can do the request
   * Since the request is cancelled onDataAvailable should not be called. The
   * onStopRequest does nothing. The convert function just returns the stream,
   * it's just the synchronous version of asyncConvertData.
   */

  // nsIStreamConverter::convert
  convert: function(aFromStream, aFromType, aToType, aCtxt) {
    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  },

  isValidRequest: function() {
    return true;
  },

  createChromeActions: function(window, urlHint) {
    var url;
    var element = window.frameElement;
    var isOverlay = false;
    var params = {};
    if (element) {
      var tagName = element.nodeName;
      while (tagName != 'EMBED' && tagName != 'OBJECT') {
        // plugin overlay skipping until the target plugin is found
        isOverlay = true;
        element = element.parentNode;
        if (!element)
          throw 'Plugin element is not found';
        tagName = element.nodeName;
      }
      if (tagName == 'EMBED') {
        params = parseQueryString(element.getAttribute('flashvars'));
        url = element.getAttribute('src');
      } else {
        for (var i = 0; i < element.childNodes.length; ++i) {
          var paramElement = element.childNodes[i];
          if (paramElement.nodeType != 1 ||
              paramElement.nodeName != 'PARAM') continue;

          params[paramElement.getAttribute('name')] = paramElement.getAttribute('value');
        }
        var dataAttribute = element.getAttribute('data');
        url = dataAttribute || params.movie || params.src;
      }
    }
    var element = window.frameElement;
    var baseUrl = element ? element.ownerDocument.location.href : null; // XXX base url?

    url = url ? combineUrl(baseUrl, url) : urlHint;

    var actions = new ChromeActions(url, params, baseUrl, window);
    actions.isOverlay = isOverlay;
    actions.isPausedAtStart = /\bpaused=true$/.test(urlHint);
    return actions;
  },

  // nsIStreamConverter::asyncConvertData
  asyncConvertData: function(aFromType, aToType, aListener, aCtxt) {
    if(!this.isValidRequest(aCtxt))
      throw Cr.NS_ERROR_NOT_IMPLEMENTED;

    // Store the listener passed to us
    this.listener = aListener;
  },

  // nsIStreamListener::onDataAvailable
  onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, aCount) {
    // Do nothing since all the data loading is handled by the viewer.
    log('SANITY CHECK: onDataAvailable SHOULD NOT BE CALLED!');
  },

  // nsIRequestObserver::onStartRequest
  onStartRequest: function(aRequest, aContext) {

    // Setup the request so we can use it below.
    aRequest.QueryInterface(Ci.nsIChannel);
    // Cancel the request so the viewer can handle it.
    aRequest.cancel(Cr.NS_BINDING_ABORTED);

    var originalURI = aRequest.URI;

    // checking if the plug-in shall be run in simple mode
    var isSimpleMode = originalURI.spec === EXPECTED_PLAYPREVIEW_URI_PREFIX &&
      getBoolPref('shumway.simpleMode', false);

    // Create a new channel that is viewer loaded as a resource.
    var ioService = Services.io;
    var channel = ioService.newChannel(isSimpleMode ?
                    'resource://shumway/web/simple.html' :
                    'resource://shumway/web/viewer.html', null, null);

    var converter = this;
    var listener = this.listener;
    // Proxy all the request observer calls, when it gets to onStopRequest
    // we can get the dom window.
    var proxy = {
      onStartRequest: function() {
        listener.onStartRequest.apply(listener, arguments);
      },
      onDataAvailable: function() {
        listener.onDataAvailable.apply(listener, arguments);
      },
      onStopRequest: function() {
        var domWindow = getDOMWindow(channel);
        // Double check the url is still the correct one.
        if (domWindow.document.documentURIObject.equals(channel.originalURI)) {
          let actions = converter.createChromeActions(domWindow,
                                                      originalURI.spec);
          let requestListener = new RequestListener(actions);
          domWindow.addEventListener('shumway.message', function(event) {
            requestListener.receive(event);
          }, false, true);
        }
        listener.onStopRequest.apply(listener, arguments);
      }
    };

    // XXX? Keep the URL the same so the browser sees it as the same.
    // channel.originalURI = aRequest.URI;
    channel.asyncOpen(proxy, aContext);
  },

  // nsIRequestObserver::onStopRequest
  onStopRequest: function(aRequest, aContext, aStatusCode) {
    // Do nothing.
  }
};

// properties required for XPCOM registration:
function copyProperties(obj, template) {
  for (var prop in template) {
    obj[prop] = template[prop];
  }
}

function FlashStreamConverter1() {}
FlashStreamConverter1.prototype = new FlashStreamConverterBase();
copyProperties(FlashStreamConverter1.prototype, {
  classID: Components.ID('{4c6030f7-e20a-264f-5b0e-ada3a9e97384}'),
  classDescription: 'Shumway Content Converter Component',
  contractID: '@mozilla.org/streamconv;1?from=application/x-shockwave-flash&to=*/*'
});

function FlashStreamConverter2() {}
FlashStreamConverter2.prototype = new FlashStreamConverterBase();
copyProperties(FlashStreamConverter2.prototype, {
  classID: Components.ID('{4c6030f7-e20a-264f-5f9b-ada3a9e97384}'),
  classDescription: 'Shumway PlayPreview Component',
  contractID: '@mozilla.org/streamconv;1?from=application/x-moz-playpreview&to=*/*'
});
FlashStreamConverter2.prototype.isValidRequest =
  (function(aCtxt) {
    try {
      var request = aCtxt;
      request.QueryInterface(Ci.nsIChannel);
      var spec = request.URI.spec;
      return spec.indexOf(EXPECTED_PLAYPREVIEW_URI_PREFIX) === 0;
    } catch (e) {
      return false;
    }
  });

var NSGetFactory1 = XPCOMUtils.generateNSGetFactory([FlashStreamConverter1]);
var NSGetFactory2 = XPCOMUtils.generateNSGetFactory([FlashStreamConverter2]);
<|endoftext|>


<commit_before>
/**
 * Module dependencies.
 */

var debug = require('debug')('wpcom:tag');

/**
 * Tag methods
 *
 * @param {String} [slug]
 * @param {String} sid site id
 * @param {WPCOM} wpcom
 * @api public
 */

function Tag(slug, sid, wpcom){
  if (!sid) {
    throw new Error('`side id` is not correctly defined');
  }

  if (!(this instanceof Tag)) return new Tag(slug, sid, wpcom);

  this.wpcom = wpcom;
  this._sid = sid;
  this._slug = slug;
}

/**
 * Set tag `slug`
 *
 * @param {String} slug
 * @api public
 */

Tag.prototype.slug = function(slug){
  this._slug = slug;
};

/**
 * Get tag
 *
 * @param {Object} [query]
 * @param {Function} fn
 * @api public
 */

Tag.prototype.get = function(query, fn){
  var path = '/sites/' + this._sid + '/tags/slug:' + this._slug;
  return this.wpcom.sendRequest(path, query, null, fn);
};

/**
 * Add tag
 *
 * @param {Object} body
 * @param {Function} fn
 * @api public
 */

Tag.prototype.add = function(body, fn){
  var path = '/sites/' + this._sid + '/tags/new';
  return this.wpcom.sendRequest({ path: path, method: 'post' }, null, body, fn);
};

/**
 * Edit tag
 *
 * @param {Object} body
 * @param {Function} fn
 * @api public
 */

Tag.prototype.update = function(body, fn){
  var path = '/sites/' + this._sid + '/tags/slug:' + this._slug;
  return this.wpcom.sendRequest({ path: path, method: 'post' }, null, body, fn);
};

/**
 * Delete tag
 *
 * @param {Function} fn
 * @api public
 */

Tag.prototype['delete'] =
Tag.prototype.del = function(fn){
  var path = '/sites/' + this._sid + '/tags/slug:' + this._slug + '/delete';
  return this.wpcom.sendRequest({ path: path, method: 'post' }, null, null, fn);
};

/**
 * Expose `Tag` module
 */

module.exports = Tag;
<commit_msg>tag: update coding<commit_after>
/**
 * Module dependencies.
 */

var debug = require('debug')('wpcom:tag');

/**
 * Tag methods
 *
 * @param {String} [slug]
 * @param {String} sid site id
 * @param {WPCOM} wpcom
 * @api public
 */

function Tag(slug, sid, wpcom) {
  if (!sid) {
    throw new Error('`side id` is not correctly defined');
  }

  if (!(this instanceof Tag)) {
    return new Tag(slug, sid, wpcom);
  }

  this.wpcom = wpcom;
  this._sid = sid;
  this._slug = slug;
}

/**
 * Set tag `slug`
 *
 * @param {String} slug
 * @api public
 */

Tag.prototype.slug = function (slug) {
  this._slug = slug;
};

/**
 * Get tag
 *
 * @param {Object} [query]
 * @param {Function} fn
 * @api public
 */

Tag.prototype.get = function (query, fn) {
  var path = '/sites/' + this._sid + '/tags/slug:' + this._slug;
  return this.wpcom.sendRequest(path, query, null, fn);
};

/**
 * Add tag
 *
 * @param {Object} body
 * @param {Function} fn
 * @api public
 */

Tag.prototype.add = function (body, fn) {
  var path = '/sites/' + this._sid + '/tags/new';
  return this.wpcom.sendRequest({ path: path, method: 'post' }, null, body, fn);
};

/**
 * Edit tag
 *
 * @param {Object} body
 * @param {Function} fn
 * @api public
 */

Tag.prototype.update = function (body, fn) {
  var path = '/sites/' + this._sid + '/tags/slug:' + this._slug;
  return this.wpcom.sendRequest({ path: path, method: 'post' }, null, body, fn);
};

/**
 * Delete tag
 *
 * @param {Function} fn
 * @api public
 */

Tag.prototype['delete'] = Tag.prototype.del = function (fn) {
  var path = '/sites/' + this._sid + '/tags/slug:' + this._slug + '/delete';
  return this.wpcom.sendRequest({ path: path, method: 'post' }, null, null, fn);
};

/**
 * Expose `Tag` module
 */

module.exports = Tag;<|endoftext|>


<commit_before>'use strict';
/* global module */

/**
 * Shared library to wrap around vega code
 * @param {Object} load Vega loader object to use and override
 * @param {boolean} useXhr true if we should use XHR, false for node.js http loading
 * @param {boolean} isTrusted true if the graph spec can be trusted
 * @param {string[]} httpDomains list of allowed http domains
 * @param {string[]} httpsDomains list of allowed https domains
 * @param {Object} domainMap domain remapping
 * @param {Function} logger
 * @param {Function} objExtender $.extend in browser, _.extend in NodeJs
 * @param {Function} parseUrl
 * @param {Function} formatUrl
 * @constructor
 */
function VegaWrapper(load, useXhr, isTrusted, httpDomains, httpsDomains, domainMap, logger, objExtender, parseUrl, formatUrl) {
    var self = this;
    self.isTrusted = isTrusted;
    self.logger = logger;
    self.objExtender = objExtender;
    self.parseUrl = parseUrl;
    self.formatUrl = formatUrl;

    // Convert domains to a regex:  (any-subdomain)\.(wikipedia\.org|wikivoyage\.org|...)
    function makeValidator(domains) {
        if (!domains || domains.length === 0) return {
            // Optimization - always return false
            test: function () {
                return false;
            }
        };
        return new RegExp('^([^@/:]*\.)?(' +
            domains
                .map(function (s) {
                    return s.replace('.', '\\.');
                })
                .join('|') + ')$', 'i');
    }

    self.httpHostsRe = makeValidator(httpDomains);
    self.httpsHostsRe = makeValidator(httpsDomains);
    self.domainMap = domainMap;

    load.loader = function (opt, callback) {
        var error = callback || function (e) { throw e; }, url;

        try {
            url = self.sanitizeUrl(opt); // enable override
        } catch (err) {
            error(err);
            return;
        }

        // Process data response
        var cb = function (error, data) {
            return self.dataParser(error, data, opt, callback);
        };

        if (useXhr) {
            return load.xhr(url, opt, cb);
        } else {
            return load.http(url, opt, cb);
        }
    };
}

module.exports = VegaWrapper;

/**
 * Check if host was listed in the allowed domains, normalize it, and get correct protocol
 * @param {string} host
 * @returns {Object}
 */
VegaWrapper.prototype.sanitizeHost = function (host) {
    // First, map the host
    host = (this.domainMap && this.domainMap[host]) || host;

    var result = {
        host: host
    };

    if (this.httpsHostsRe.test(host)) {
        result.protocol = 'https';
    } else if (this.httpHostsRe.test(host)) {
        result.protocol = 'http';
    } else {
        result = undefined;
    }

    return result;
};

/**this
 * Validate and update urlObj to be safe for client-side and server-side usage
 * @param {Object} opt passed by the vega loader. May be altered with optional "isApiCall" and "extractApiContent"
 * @returns {boolean} true on success
 */
VegaWrapper.prototype.sanitizeUrl = function (opt) {
    var urlParts = this.parseUrl(opt);

    var sanitizedHost = this.sanitizeHost(urlParts.host);
    if (!sanitizedHost) {
        throw new Error('URL hostname is not whitelisted: ' + JSON.stringify(opt.url));
    }
    urlParts.host = sanitizedHost.host;
    if (!urlParts.protocol) {
        // Update protocol-relative URLs
        urlParts.protocol = sanitizedHost.protocol;
    }

    switch (urlParts.protocol) {
        case 'http':
        case 'https':
            if (!this.isTrusted) {
                throw new Error('HTTP and HTTPS protocols not supported for untrusted graphs');
            }
            // keep the original URL
            break;

        case 'wikiapi':
            // wikiapi:///?action=query&list=allpages
            // Call to api.php - ignores the path parameter, and only uses the query
            urlParts.query = this.objExtender(urlParts.query, {format: 'json', formatversion: '2'});
            urlParts.pathname = '/w/api.php';
            urlParts.protocol = sanitizedHost.protocol;
            opt.isApiCall = true;
            break;

        case 'wikirest':
            // wikirest:///api/rest_v1/page/...
            // Call to RESTbase api - requires the path to start with "/api/"
            // The /api/... path is safe for GET requests
            if (!/^\/api\//.test(urlParts.pathname)) {
                throw new Error('wikirest: protocol must begin with the /api/ prefix');
            }
            // keep urlParts.query
            // keep urlParts.pathname
            urlParts.protocol = sanitizedHost.protocol;
            break;

        case 'wikiraw':
            // wikiraw:///MyPage/data
            // Get raw content of a wiki page, where the path is the title
            // of the page with an additional leading '/' which gets removed.
            // Uses mediawiki api, and extract the content after the request
            // Query value must be a valid MediaWiki title string, but we only ensure
            // there is no pipe symbol, the rest is handlered by the api.
            if (!/^\/[^|]+$/.test(urlParts.pathname)) {
                throw new Error('wikiraw: invalid title');
            }
            urlParts.query = {
                format: 'json',
                formatversion: '2',
                action: 'query',
                prop: 'revisions',
                rvprop: 'content',
                titles: decodeURIComponent(urlParts.pathname.substring(1))
            };
            urlParts.pathname = '/w/api.php';
            urlParts.protocol = sanitizedHost.protocol;
            opt.isApiCall = true;
            opt.extractApiContent = true;
            break;

        case 'wikirawupload':
            // wikirawupload://upload.wikimedia.org/wikipedia/commons/3/3e/Einstein_1921.jpg
            // Get an image for the graph, e.g. from commons
            // This tag specifies any content from the uploads.* domain, without query params
            if (!/^upload\./.test(urlParts.host)) {
                throw new Error('wikirawupload: protocol must reference upload.* host: ' + JSON.stringify(opt.url));
            }
            urlParts.query = null;
            // keep urlParts.pathname;
            urlParts.protocol = sanitizedHost.protocol;
            break;

        default:
            throw new Error('Unknown protocol ' + JSON.stringify(opt.url));
    }
    return this.formatUrl(urlParts);
};

/**
 * Performs post-processing of the data requested by the graph's spec
 */
VegaWrapper.prototype.dataParser = function (error, data, opt, callback) {
    if (error) {
        callback(error);
        return;
    }
    if (opt.isApiCall) {
        // This was an API call - check for errors
        var json = JSON.parse(data);
        if (json.error) {
            error = new Error('API error: ' + JSON.stringify(json.error));
            data = undefined;
        } else {
            if (json.warnings) {
                this.logger('API warnings: ' + JSON.stringify(json.warnings));
            }
            if (opt.extractApiContent) {
                try {
                    data = json.query.pages[0].revisions[0].content;
                } catch (e) {
                    data = undefined;
                    error = new Error('Page content not available ' + opt.url);
                }
            }
        }
    }
    callback(error, data);
};
<commit_msg>better error message<commit_after>'use strict';
/* global module */

/**
 * Shared library to wrap around vega code
 * @param {Object} load Vega loader object to use and override
 * @param {boolean} useXhr true if we should use XHR, false for node.js http loading
 * @param {boolean} isTrusted true if the graph spec can be trusted
 * @param {string[]} httpDomains list of allowed http domains
 * @param {string[]} httpsDomains list of allowed https domains
 * @param {Object} domainMap domain remapping
 * @param {Function} logger
 * @param {Function} objExtender $.extend in browser, _.extend in NodeJs
 * @param {Function} parseUrl
 * @param {Function} formatUrl
 * @constructor
 */
function VegaWrapper(load, useXhr, isTrusted, httpDomains, httpsDomains, domainMap, logger, objExtender, parseUrl, formatUrl) {
    var self = this;
    self.isTrusted = isTrusted;
    self.logger = logger;
    self.objExtender = objExtender;
    self.parseUrl = parseUrl;
    self.formatUrl = formatUrl;

    // Convert domains to a regex:  (any-subdomain)\.(wikipedia\.org|wikivoyage\.org|...)
    function makeValidator(domains) {
        if (!domains || domains.length === 0) return {
            // Optimization - always return false
            test: function () {
                return false;
            }
        };
        return new RegExp('^([^@/:]*\.)?(' +
            domains
                .map(function (s) {
                    return s.replace('.', '\\.');
                })
                .join('|') + ')$', 'i');
    }

    self.httpHostsRe = makeValidator(httpDomains);
    self.httpsHostsRe = makeValidator(httpsDomains);
    self.domainMap = domainMap;

    load.loader = function (opt, callback) {
        var error = callback || function (e) { throw e; }, url;

        try {
            url = self.sanitizeUrl(opt); // enable override
        } catch (err) {
            error(err);
            return;
        }

        // Process data response
        var cb = function (error, data) {
            return self.dataParser(error, data, opt, callback);
        };

        if (useXhr) {
            return load.xhr(url, opt, cb);
        } else {
            return load.http(url, opt, cb);
        }
    };
}

module.exports = VegaWrapper;

/**
 * Check if host was listed in the allowed domains, normalize it, and get correct protocol
 * @param {string} host
 * @returns {Object}
 */
VegaWrapper.prototype.sanitizeHost = function (host) {
    // First, map the host
    host = (this.domainMap && this.domainMap[host]) || host;

    var result = {
        host: host
    };

    if (this.httpsHostsRe.test(host)) {
        result.protocol = 'https';
    } else if (this.httpHostsRe.test(host)) {
        result.protocol = 'http';
    } else {
        result = undefined;
    }

    return result;
};

/**this
 * Validate and update urlObj to be safe for client-side and server-side usage
 * @param {Object} opt passed by the vega loader. May be altered with optional "isApiCall" and "extractApiContent"
 * @returns {boolean} true on success
 */
VegaWrapper.prototype.sanitizeUrl = function (opt) {
    var urlParts = this.parseUrl(opt);

    var sanitizedHost = this.sanitizeHost(urlParts.host);
    if (!sanitizedHost) {
        throw new Error('URL hostname is not whitelisted: ' + JSON.stringify(opt.url));
    }
    urlParts.host = sanitizedHost.host;
    if (!urlParts.protocol) {
        // Update protocol-relative URLs
        urlParts.protocol = sanitizedHost.protocol;
    }

    switch (urlParts.protocol) {
        case 'http':
        case 'https':
            if (!this.isTrusted) {
                throw new Error('HTTP and HTTPS protocols are not supported for untrusted graphs.\n' +
                    'Use wikiraw:, wikiapi:, wikirest:, and wikirawupload: protocols.\n' +
                    'See https://www.mediawiki.org/wiki/Extension:Graph#External_data');
            }
            // keep the original URL
            break;

        case 'wikiapi':
            // wikiapi:///?action=query&list=allpages
            // Call to api.php - ignores the path parameter, and only uses the query
            urlParts.query = this.objExtender(urlParts.query, {format: 'json', formatversion: '2'});
            urlParts.pathname = '/w/api.php';
            urlParts.protocol = sanitizedHost.protocol;
            opt.isApiCall = true;
            break;

        case 'wikirest':
            // wikirest:///api/rest_v1/page/...
            // Call to RESTbase api - requires the path to start with "/api/"
            // The /api/... path is safe for GET requests
            if (!/^\/api\//.test(urlParts.pathname)) {
                throw new Error('wikirest: protocol must begin with the /api/ prefix');
            }
            // keep urlParts.query
            // keep urlParts.pathname
            urlParts.protocol = sanitizedHost.protocol;
            break;

        case 'wikiraw':
            // wikiraw:///MyPage/data
            // Get raw content of a wiki page, where the path is the title
            // of the page with an additional leading '/' which gets removed.
            // Uses mediawiki api, and extract the content after the request
            // Query value must be a valid MediaWiki title string, but we only ensure
            // there is no pipe symbol, the rest is handlered by the api.
            if (!/^\/[^|]+$/.test(urlParts.pathname)) {
                throw new Error('wikiraw: invalid title');
            }
            urlParts.query = {
                format: 'json',
                formatversion: '2',
                action: 'query',
                prop: 'revisions',
                rvprop: 'content',
                titles: decodeURIComponent(urlParts.pathname.substring(1))
            };
            urlParts.pathname = '/w/api.php';
            urlParts.protocol = sanitizedHost.protocol;
            opt.isApiCall = true;
            opt.extractApiContent = true;
            break;

        case 'wikirawupload':
            // wikirawupload://upload.wikimedia.org/wikipedia/commons/3/3e/Einstein_1921.jpg
            // Get an image for the graph, e.g. from commons
            // This tag specifies any content from the uploads.* domain, without query params
            if (!/^upload\./.test(urlParts.host)) {
                throw new Error('wikirawupload: protocol must reference upload.* host: ' + JSON.stringify(opt.url));
            }
            urlParts.query = null;
            // keep urlParts.pathname;
            urlParts.protocol = sanitizedHost.protocol;
            break;

        default:
            throw new Error('Unknown protocol ' + JSON.stringify(opt.url));
    }
    return this.formatUrl(urlParts);
};

/**
 * Performs post-processing of the data requested by the graph's spec
 */
VegaWrapper.prototype.dataParser = function (error, data, opt, callback) {
    if (error) {
        callback(error);
        return;
    }
    if (opt.isApiCall) {
        // This was an API call - check for errors
        var json = JSON.parse(data);
        if (json.error) {
            error = new Error('API error: ' + JSON.stringify(json.error));
            data = undefined;
        } else {
            if (json.warnings) {
                this.logger('API warnings: ' + JSON.stringify(json.warnings));
            }
            if (opt.extractApiContent) {
                try {
                    data = json.query.pages[0].revisions[0].content;
                } catch (e) {
                    data = undefined;
                    error = new Error('Page content not available ' + opt.url);
                }
            }
        }
    }
    callback(error, data);
};
<|endoftext|>


<commit_before>/* global NAF */
var ChildEntityCache = require('./ChildEntityCache');

class NetworkEntities {

  constructor() {
    this.entities = {};
    this.childCache = new ChildEntityCache();
    this.onRemoteEntityCreatedEvent = new Event('remoteEntityCreated');
    this._persistentFirstSyncs = {};
  }

  registerEntity(networkId, entity) {
    this.entities[networkId] = entity;
  }

  createRemoteEntity(entityData) {
    NAF.log.write('Creating remote entity', entityData);

    var networkId = entityData.networkId;
    var el = NAF.schemas.getCachedTemplate(entityData.template);

    this.initPosition(el, entityData.components);
    this.initRotation(el, entityData.components);
    this.addNetworkComponent(el, entityData);

    this.registerEntity(networkId, el);

    return el;
  }

  initPosition(entity, componentData) {
    var hasPosition = componentData.hasOwnProperty('position');
    if (hasPosition) {
      var position = componentData.position;
      entity.setAttribute('position', position);
    }
  }

  initRotation(entity, componentData) {
    var hasRotation = componentData.hasOwnProperty('rotation');
    if (hasRotation) {
      var rotation = componentData.rotation;
      entity.setAttribute('rotation', rotation);
    }
  }

  addNetworkComponent(entity, entityData) {
    var networkData = {
      template: entityData.template,
      creator: entityData.creator,
      owner: entityData.owner,
      networkId: entityData.networkId,
      persistent: entityData.persistent
    };

    entity.setAttribute('networked', networkData);
    entity.firstUpdateData = entityData;
  }

  updateEntityMulti(client, dataType, entityDatas, source) {
    if (NAF.options.syncSource && source !== NAF.options.syncSource) return;
    for (let i = 0, l = entityDatas.d.length; i < l; i++) {
      this.updateEntity(client, 'u', entityDatas.d[i], source);
    }
  }

  updateEntity(client, dataType, entityData, source) {
    if (NAF.options.syncSource && source !== NAF.options.syncSource) return;
    var networkId = entityData.networkId;

    if (this.hasEntity(networkId)) {
      this.entities[networkId].components.networked.networkUpdate(entityData);
    } else if (entityData.isFirstSync) {
      if (NAF.options.firstSyncSource && source !== NAF.options.firstSyncSource) {
        NAF.log.write('Ignoring first sync from disallowed source', source);
      } else {
        if (entityData.persistent) {
          // If we receive a firstSync for a persistent entity that we don't have yet,
          // we assume the scene will create it at some point, so stash the update for later use.
          this._persistentFirstSyncs[networkId] = entityData;
        } else {
          this.receiveFirstUpdateFromEntity(entityData);
        }
      }
    }
  }

  receiveFirstUpdateFromEntity(entityData) {
    var parent = entityData.parent;
    var networkId = entityData.networkId;

    var parentNotCreatedYet = parent && !this.hasEntity(parent);
    if (parentNotCreatedYet) {
      this.childCache.addChild(parent, entityData);
    } else {
      var remoteEntity = this.createRemoteEntity(entityData);
      this.createAndAppendChildren(networkId, remoteEntity);
      this.addEntityToPage(remoteEntity, parent);
    }
  }

  createAndAppendChildren(parentId, parentEntity) {
    var children = this.childCache.getChildren(parentId);
    for (var i = 0; i < children.length; i++) {
      var childEntityData = children[i];
      var childId = childEntityData.networkId;
      if (this.hasEntity(childId)) {
        NAF.log.warn(
          'Tried to instantiate entity multiple times',
          childId,
          childEntityData,
          'Existing entity:',
          this.getEntity(childId)
        );
        continue;
      }
      var childEntity = this.createRemoteEntity(childEntityData);
      this.createAndAppendChildren(childId, childEntity);
      parentEntity.appendChild(childEntity);
    }
  }

  addEntityToPage(entity, parentId) {
    if (this.hasEntity(parentId)) {
      this.addEntityToParent(entity, parentId);
    } else {
      this.addEntityToSceneRoot(entity);
    }
  }

  addEntityToParent(entity, parentId) {
    this.entities[parentId].appendChild(entity);
  }

  addEntityToSceneRoot(el) {
    var scene = document.querySelector('a-scene');
    scene.appendChild(el);
  }

  completeSync(targetClientId, isFirstSync) {
    for (var id in this.entities) {
      if (this.entities.hasOwnProperty(id)) {
        this.entities[id].components.networked.syncAll(targetClientId, isFirstSync);
      }
    }
  }

  removeRemoteEntity(toClient, dataType, data, source) {
    if (NAF.options.syncSource && source !== NAF.options.syncSource) return;
    var id = data.networkId;
    return this.removeEntity(id);
  }

  removeEntitiesOfClient(clientId) {
    var entityList = [];
    for (var id in this.entities) {
      var entityCreator = NAF.utils.getCreator(this.entities[id]);
      if (entityCreator === clientId) {
        let persists;
        const component = this.entities[id].getAttribute('networked');
        if (component && component.persistent) {
          persists = NAF.utils.isMine(this.entities[id]) || NAF.utils.takeOwnership(this.entities[id]);
        }
        if (!persists) {
          var entity = this.removeEntity(id);
          entityList.push(entity);
        }
      }
    }
    return entityList;
  }

  removeEntity(id) {
    this.forgetPersistentFirstSync(id);

    if (this.hasEntity(id)) {
      var entity = this.entities[id];
      this.forgetEntity(id);
      entity.parentNode.removeChild(entity);
      return entity;
    } else {
      NAF.log.error("Tried to remove entity I don't have.");
      return null;
    }
  }

  forgetEntity(id){
    delete this.entities[id];
    this.forgetPersistentFirstSync(id);
  }

  getPersistentFirstSync(id){
    return this._persistentFirstSyncs[id];
  }

  forgetPersistentFirstSync(id){
    delete this._persistentFirstSyncs[id];
  }

  getEntity(id) {
    if (this.entities.hasOwnProperty(id)) {
      return this.entities[id];
    }
    return null;
  }

  hasEntity(id) {
    return this.entities.hasOwnProperty(id);
  }

  removeRemoteEntities() {
    this.childCache = new ChildEntityCache();

    for (var id in this.entities) {
      var owner = this.entities[id].getAttribute('networked').owner;
      if (owner != NAF.clientId) {
        this.removeEntity(id);
      }
    }
  }
}

module.exports = NetworkEntities;
<commit_msg>explicitly check if the owner of an entity being firstSync'd is still connected to prevent instatiation after disconnect<commit_after>/* global NAF */
var ChildEntityCache = require('./ChildEntityCache');

class NetworkEntities {

  constructor() {
    this.entities = {};
    this.childCache = new ChildEntityCache();
    this.onRemoteEntityCreatedEvent = new Event('remoteEntityCreated');
    this._persistentFirstSyncs = {};
  }

  registerEntity(networkId, entity) {
    this.entities[networkId] = entity;
  }

  createRemoteEntity(entityData) {
    NAF.log.write('Creating remote entity', entityData);

    var networkId = entityData.networkId;
    var el = NAF.schemas.getCachedTemplate(entityData.template);

    this.initPosition(el, entityData.components);
    this.initRotation(el, entityData.components);
    this.addNetworkComponent(el, entityData);

    this.registerEntity(networkId, el);

    return el;
  }

  initPosition(entity, componentData) {
    var hasPosition = componentData.hasOwnProperty('position');
    if (hasPosition) {
      var position = componentData.position;
      entity.setAttribute('position', position);
    }
  }

  initRotation(entity, componentData) {
    var hasRotation = componentData.hasOwnProperty('rotation');
    if (hasRotation) {
      var rotation = componentData.rotation;
      entity.setAttribute('rotation', rotation);
    }
  }

  addNetworkComponent(entity, entityData) {
    var networkData = {
      template: entityData.template,
      creator: entityData.creator,
      owner: entityData.owner,
      networkId: entityData.networkId,
      persistent: entityData.persistent
    };

    entity.setAttribute('networked', networkData);
    entity.firstUpdateData = entityData;
  }

  updateEntityMulti(client, dataType, entityDatas, source) {
    if (NAF.options.syncSource && source !== NAF.options.syncSource) return;
    for (let i = 0, l = entityDatas.d.length; i < l; i++) {
      this.updateEntity(client, 'u', entityDatas.d[i], source);
    }
  }

  updateEntity(client, dataType, entityData, source) {
    if (NAF.options.syncSource && source !== NAF.options.syncSource) return;
    var networkId = entityData.networkId;

    const entityOwnerActive = NAF.connection.activeDataChannels[entityData.owner] === false;
    if (this.hasEntity(networkId)) {
      this.entities[networkId].components.networked.networkUpdate(entityData);
    } else if (entityData.isFirstSync && entityOwnerActive) {
      if (NAF.options.firstSyncSource && source !== NAF.options.firstSyncSource) {
        NAF.log.write('Ignoring first sync from disallowed source', source);
      } else {
        if (entityData.persistent) {
          // If we receive a firstSync for a persistent entity that we don't have yet,
          // we assume the scene will create it at some point, so stash the update for later use.
          this._persistentFirstSyncs[networkId] = entityData;
        } else {
          this.receiveFirstUpdateFromEntity(entityData);
        }
      }
    }
  }

  receiveFirstUpdateFromEntity(entityData) {
    var parent = entityData.parent;
    var networkId = entityData.networkId;

    var parentNotCreatedYet = parent && !this.hasEntity(parent);
    if (parentNotCreatedYet) {
      this.childCache.addChild(parent, entityData);
    } else {
      var remoteEntity = this.createRemoteEntity(entityData);
      this.createAndAppendChildren(networkId, remoteEntity);
      this.addEntityToPage(remoteEntity, parent);
    }
  }

  createAndAppendChildren(parentId, parentEntity) {
    var children = this.childCache.getChildren(parentId);
    for (var i = 0; i < children.length; i++) {
      var childEntityData = children[i];
      var childId = childEntityData.networkId;
      if (this.hasEntity(childId)) {
        NAF.log.warn(
          'Tried to instantiate entity multiple times',
          childId,
          childEntityData,
          'Existing entity:',
          this.getEntity(childId)
        );
        continue;
      }
      var childEntity = this.createRemoteEntity(childEntityData);
      this.createAndAppendChildren(childId, childEntity);
      parentEntity.appendChild(childEntity);
    }
  }

  addEntityToPage(entity, parentId) {
    if (this.hasEntity(parentId)) {
      this.addEntityToParent(entity, parentId);
    } else {
      this.addEntityToSceneRoot(entity);
    }
  }

  addEntityToParent(entity, parentId) {
    this.entities[parentId].appendChild(entity);
  }

  addEntityToSceneRoot(el) {
    var scene = document.querySelector('a-scene');
    scene.appendChild(el);
  }

  completeSync(targetClientId, isFirstSync) {
    for (var id in this.entities) {
      if (this.entities.hasOwnProperty(id)) {
        this.entities[id].components.networked.syncAll(targetClientId, isFirstSync);
      }
    }
  }

  removeRemoteEntity(toClient, dataType, data, source) {
    if (NAF.options.syncSource && source !== NAF.options.syncSource) return;
    var id = data.networkId;
    return this.removeEntity(id);
  }

  removeEntitiesOfClient(clientId) {
    var entityList = [];
    for (var id in this.entities) {
      var entityCreator = NAF.utils.getCreator(this.entities[id]);
      if (entityCreator === clientId) {
        let persists;
        const component = this.entities[id].getAttribute('networked');
        if (component && component.persistent) {
          persists = NAF.utils.isMine(this.entities[id]) || NAF.utils.takeOwnership(this.entities[id]);
        }
        if (!persists) {
          var entity = this.removeEntity(id);
          entityList.push(entity);
        }
      }
    }
    return entityList;
  }

  removeEntity(id) {
    this.forgetPersistentFirstSync(id);

    if (this.hasEntity(id)) {
      var entity = this.entities[id];
      this.forgetEntity(id);
      entity.parentNode.removeChild(entity);
      return entity;
    } else {
      NAF.log.error("Tried to remove entity I don't have.");
      return null;
    }
  }

  forgetEntity(id){
    delete this.entities[id];
    this.forgetPersistentFirstSync(id);
  }

  getPersistentFirstSync(id){
    return this._persistentFirstSyncs[id];
  }

  forgetPersistentFirstSync(id){
    delete this._persistentFirstSyncs[id];
  }

  getEntity(id) {
    if (this.entities.hasOwnProperty(id)) {
      return this.entities[id];
    }
    return null;
  }

  hasEntity(id) {
    return this.entities.hasOwnProperty(id);
  }

  removeRemoteEntities() {
    this.childCache = new ChildEntityCache();

    for (var id in this.entities) {
      var owner = this.entities[id].getAttribute('networked').owner;
      if (owner != NAF.clientId) {
        this.removeEntity(id);
      }
    }
  }
}

module.exports = NetworkEntities;
<|endoftext|>


<commit_before>'use strict';
exports.info = {
  desc: 'Get the Twitch ID for a specified user.',
  usage: '<name|id>',
  aliases: [],
  permissions: ['SEND_MESSAGES'],
};

const request = require('snekfetch');

exports.run = (client, msg, params = []) => new Promise(async (resolve, reject) => {
  if (params.length === 0) {
    client.utils.usage(msg, exports.info).then(resolve).catch(reject);
    return;
  }
  const query = params.join(' ').trim().toLowerCase();
  const user = await client.twitch.getUserData(query);
  if (!user) {
    msg.channel.send(`The Twitch channel **${params[0]}** does not exist.`).then(resolve).catch(reject);
    return;
  }

  const uri = client.utils.buildUri('https://api.twitch.tv/helix/subscriptions', {
    user_id: user.id,
    broadcaster_id: client.config.twitch.id,
  });
  const subscription = client.utils.get(['body', 'data', 0],
      await request.get(uri)
          .set({
            'Client-ID': client.config.twitch.client_id,
            Authorization: `Bearer ${client.config.twitch.access_token}`,
          }));

  const name = client.twitch.displayName(user);
  const type = `\n${user.broadcaster_type}`;
  const sub = `\nSubscribed: ${Boolean(subscription)}`;
  const str = /^\d+$/.test(query) ?
    `${user.id} => **${name}**${client.utils.capitalize(type)}${sub}}` :
    `${name} => **${user.id}**${client.utils.capitalize(type)}${sub}}`;
  msg.channel.send(str)
    .then(resolve).catch(reject);
});
<commit_msg>remove extra }<commit_after>'use strict';
exports.info = {
  desc: 'Get the Twitch ID for a specified user.',
  usage: '<name|id>',
  aliases: [],
  permissions: ['SEND_MESSAGES'],
};

const request = require('snekfetch');

exports.run = (client, msg, params = []) => new Promise(async (resolve, reject) => {
  if (params.length === 0) {
    client.utils.usage(msg, exports.info).then(resolve).catch(reject);
    return;
  }
  const query = params.join(' ').trim().toLowerCase();
  const user = await client.twitch.getUserData(query);
  if (!user) {
    msg.channel.send(`The Twitch channel **${params[0]}** does not exist.`).then(resolve).catch(reject);
    return;
  }

  const uri = client.utils.buildUri('https://api.twitch.tv/helix/subscriptions', {
    user_id: user.id,
    broadcaster_id: client.config.twitch.id,
  });
  const subscription = client.utils.get(['body', 'data', 0],
      await request.get(uri)
          .set({
            'Client-ID': client.config.twitch.client_id,
            Authorization: `Bearer ${client.config.twitch.access_token}`,
          }));

  const name = client.twitch.displayName(user);
  const type = `\n${client.utils.capitalize(user.broadcaster_type)}`;
  const sub = `\nSubscribed: ${Boolean(subscription)}`;
  let str = /^\d+$/.test(query) ? `${user.id} => **${name}**` : `${name} => **${user.id}**`;
  if (user.broadcaster_type) str += type;
  if (subscription) str += sub;
  msg.channel.send(str)
    .then(resolve).catch(reject);
});
<|endoftext|>


<commit_before>importScripts('../dist/yatern.js');

addEventListener('message', function (e) {
    var data = e.data;
    //console.log(e.data.code);
    var t1 = (new Date).getTime();
    var gObject = YAtern.analyze(e.data.code);
    var t2 = (new Date).getTime();
    console.log("Time spent: " + (t2 - t1));

    var message = {};

    var typeNames = '';
    var propNames = '';

    var types = gObject.getProp('x').types;
    for (var i = 0; i < types.length; i++) {
        typeNames += types[i].name + '\n';
        if (types[i].props) {
            propNames += Object.getOwnPropertyNames(types[i].props) + '\n';
        }
    }
    message.typeNames = typeNames;
    message.propNames = propNames;

    postMessage(message);
});
<commit_msg>[TASK] Adjust web_tests for Map and Set data structure.<commit_after>importScripts('../dist/yatern.js');

addEventListener('message', function (e) {
    var t1 = (new Date).getTime();
    var gObject = YAtern.analyze(e.data.code);
    var t2 = (new Date).getTime();
    console.log("Time spent: " + (t2 - t1));

    var message = {};

    var typeNames = '';
    var propNames = '';

    var types = gObject.getProp('x').types;

    types.forEach(function (type) {
        typeNames += type.name + ', ';
        if (type.props) {
            type.props.forEach(function (value, key) {
                propNames += key + ', ';
            });
        }
    });
    message.typeNames = typeNames;
    message.propNames = propNames;

    postMessage(message);
});
<|endoftext|>


<commit_before>'use strict'

const test = require('tape')
const SolidResponse = require('solid-web-client/lib/models/response')
const acls = require('../../src/index')  // solid-permissions module
const resourceUrl = 'https://example.com/resource1'
const sinon = require('sinon')
const deleteSpy = sinon.spy()

const mockWebClient = {
  head: (url) => {
    let response = new SolidResponse()
    response.url = url
    response.acl = '.acl'
    return Promise.resolve(response)
  },
  del: deleteSpy
}

test('clearPermissions() test', t => {
  let aclUrl = resourceUrl + '.acl'
  acls.clearPermissions(resourceUrl, mockWebClient)
    .then(() => {
      t.ok(deleteSpy.calledWith, aclUrl,
        'should result in a DELETE call to the .acl url')
      t.end()
    })
    .catch(err => {
      console.log(err)
      t.fail()
    })
})
<commit_msg>Fix typo in test<commit_after>'use strict'

const test = require('tape')
const SolidResponse = require('solid-web-client/lib/models/response')
const acls = require('../../src/index')  // solid-permissions module
const resourceUrl = 'https://example.com/resource1'
const sinon = require('sinon')
const deleteSpy = sinon.spy()

const mockWebClient = {
  head: (url) => {
    let response = new SolidResponse()
    response.url = url
    response.acl = '.acl'
    return Promise.resolve(response)
  },
  del: deleteSpy
}

test('clearPermissions() test', t => {
  let aclUrl = resourceUrl + '.acl'
  acls.clearPermissions(resourceUrl, mockWebClient)
    .then(() => {
      t.ok(deleteSpy.calledWith(aclUrl),
        'should result in a DELETE call to the .acl url')
      t.end()
    })
    .catch(err => {
      console.log(err)
      t.fail()
    })
})
<|endoftext|>


<commit_before>/**
 * @file 筛选
 * @author liangjiaying@baidu.com
 * @time 2016.09
 */
define(function (){
    var customElement = require('customElement').create();

    /**
     * build
     */
    function build () {
        var filter = new Filter({
            'filterWrap' : document.querySelector('.filter'),
            'itemWrap' : document.querySelector('.timeline-content-wrap'),
        });
        filter.init();
    }

    var util = {
        /* used multiple times */
        containReg: function(txt) {
            return new RegExp('(\\s+|^)' + txt + '(\\s+|$)');
        },
        /* check if dom has certain class */
        hasClass : function(ele, cls) {
            return ele.className.match(this.containReg(cls));
        },
        /* add certain class to dom*/
        addClass : function(ele, cls) {
            if(this.hasClass(ele, cls)) return;
            ele.className = (ele.className + ' ' + cls).trim();
        },
        /* remove certain class from dom*/
        removeClass : function(ele, cls) {
            if(!this.hasClass(ele, cls)) return;
            ele.className = ele.className.replace(this.containReg(cls), ' ').trim();
        },
        /* toggle certain class of dom*/
        toggleClass : function(ele, cls) {
            if(this.hasClass(ele, cls)){
                this.removeClass(ele, cls);
            } else {
                this.addClass(ele, cls);
            }
        }
    }
    /*
    * define a Filter,
    * opt = {
        'filterWrap' : document.querySelector('.xx1'),
        'itemWrap' : document.querySelector('.xx2'),
        'mobileWidth' : 767,
        'emptyTip' : '没有符合的内容'
    * }
    * filterWrap: mandatory. dom wrapper of filter
    * itemWrap: mandatory. dom wrapper of item
    * mobileWidth: maximum width to show wise layout
    * emptyTip: shown to user if no item applys to filter
    */
    function Filter(opt) {
        var _this = this;
        if(!opt || !opt.filterWrap || !opt.itemWrap) return;

        opt.mobileWidth = opt.mobileWidth || 767;
        opt.emptyTip = opt.emptyTip || '没有符合的内容';

        /*
        * shoot: at first time,
        * add filter color and text to default-"none"
        */
        this.init = function() {
            var initOpt = opt.filterWrap.querySelector('[data-filtertype="all"]');
            util.addClass(initOpt, 'active');
            var text = initOpt.innerText.split(' ')[0];
            opt.filterWrap.querySelector('.filter-result').innerText = '筛选：无';
        }

        /*
        * shoot: when a filter is clicked.
        * add filter color and text to selected one.
        */
        this.filterSelect = function(e) {
            var oldEle = opt.filterWrap.querySelector('.active');
            var newEle = e.target;
            util.removeClass(oldEle, 'active');
            util.addClass(e.target, 'active');
            var text = newEle.innerText.replace(newEle.querySelector('.filter-num').innerText, '');
            if(text == '查看全部') text = '无';
            opt.filterWrap.querySelector('.filter-result').innerText = '筛选：' + text;
            // in wise, when select, collapse filter
            if(window.innerWidth <= opt.mobileWidth) {
                _this.toggleFilter();
            }
            _this.applyFilter(newEle.dataset.filtertype);
        }

        /*
        * shoot: on mobile when filter btn is clicked.
        * slide up or down the whole filter.
        */
        this.toggleFilter = function() {
            var listWrap = opt.filterWrap.querySelector('.filter-list');

            // hide and show filter list only on wise
            if(window.innerWidth <= opt.mobileWidth) {

                if(util.hasClass(listWrap, 'show')) {
                    // hide filter list
                    listWrap.style.height = '0px';
                } else {
                    // show filter list
                    listWrap.style.transition = 'none';
                    listWrap.style.WebkitTransition = 'none';
                    listWrap.style.height = 'auto';
                    var height = getComputedStyle(listWrap).height;

                    // target height acquired, now start the animation
                    listWrap.style.height = '0px';
                    listWrap.style.transition = 'height 0.3s';
                    setTimeout(function() {
                        // trick: in setTimeout, or there won't be any animation
                        listWrap.style.height = height;
                    }, 10);
                }
                util.toggleClass(listWrap, 'show');
            }
        }

        /*
        * shoot: when filter btn is clicked.
        * hide items that cant pass the filter.
        */
        this.applyFilter = function(filter) {
            var num = 0;
            opt.itemWrap.querySelectorAll('.filter-item').forEach(function(item) {
                if(item.dataset.filtertype.match(util.containReg(filter)) || filter == 'all') {
                    // show item
                    num ++;
                    item.style.display = "block";
                } else {
                    // hide item
                    item.style.display = "none";
                }
            });
            if (!num) {
                // no item can be shown, add "no item" text
                if(!opt.itemWrap.querySelector('.filter-emptytip')) {
                    var emptyTip = document.createElement('div');
                    util.addClass(emptyTip, 'filter-emptytip');
                    emptyTip.innerHTML = opt.emptyTip;
                    opt.itemWrap.appendChild(emptyTip);
                } else{
                }
            } else {
                var emptyTip = opt.itemWrap.querySelector('.filter-emptytip');
                if(emptyTip) {
                    opt.itemWrap.removeChild(emptyTip);
                }
            }
            window.scrollTo(0,0);
        }

        /*
        * add click event to all filters
        * when clicked, select the filter,
        * if wise, collapse filter list.
        */
        opt.filterWrap.querySelectorAll('a').forEach(function(ele) {
            ele.addEventListener('click', _this.filterSelect);
        });

        /*
        * add click event to filter result, which show only on wise.
        * when clicked, uncollapse and collapse filter list.
        */
        opt.filterWrap.querySelector('.filter-result').addEventListener('click', _this.toggleFilter);
    }

    /**
     * 初始化
     */
    customElement.prototype.init = function() {
        this.build = build;
    };

    return customElement;
});

require(['mip-mipengine-filter'], function (filter) {
    // 引入组件需要的css文件，选填
    MIP.css.mipMipengineFilter += __inline('./mip-mipengine-filter.less');
    //注册组件
    MIP.registerMipElement('mip-mipengine-filter', filter, MIP.css.mipMipengineFilter);
});<commit_msg>update: 官网组件更新<commit_after>/**
 * @file 筛选
 * @author liangjiaying@baidu.com
 * @time 2016.09
 */
define(function () {
    var customElement = require('customElement').create();

    /**
     * build
     */
    function build() {
        var filter = new Filter({
            filterWrap: document.querySelector('.filter'),
            itemWrap: document.querySelector('.timeline-content-wrap')
        });
        filter.init();
    }

    var util = {
        // used multiple times
        containReg: function (txt) {
            return new RegExp('(\\s+|^)' + txt + '(\\s+|$)');
        },
        // check if dom has certain class
        hasClass: function (ele, cls) {
            return ele.className.match(this.containReg(cls));
        },
        // add certain class to dom
        addClass: function (ele, cls) {
            if (this.hasClass(ele, cls)) {
                return;
            }
            ele.className = (ele.className + ' ' + cls).trim();
        },
        // remove certain class from dom
        removeClass: function (ele, cls) {
            if (!this.hasClass(ele, cls)) {
                return;
            }
            ele.className = ele.className.replace(this.containReg(cls), ' ').trim();
        },
        // toggle certain class of dom
        toggleClass: function (ele, cls) {
            if (this.hasClass(ele, cls)) {
                this.removeClass(ele, cls);
            } else {
                this.addClass(ele, cls);
            }
        }
    };
    /*
    * define a Filter,
    * opt = {
        'filterWrap' : document.querySelector('.xx1'),
        'itemWrap' : document.querySelector('.xx2'),
        'mobileWidth' : 767,
        'emptyTip' : '没有符合的内容'
    * }
    * filterWrap: mandatory. dom wrapper of filter
    * itemWrap: mandatory. dom wrapper of item
    * mobileWidth: maximum width to show wise layout
    * emptyTip: shown to user if no item applys to filter
    */
    function Filter(opt) {
        var _this = this;
        if (!opt || !opt.filterWrap || !opt.itemWrap) {
            return;
        }

        opt.mobileWidth = opt.mobileWidth || 767;
        opt.emptyTip = opt.emptyTip || '没有符合的内容';

        /*
        * shoot: at first time,
        * add filter color and text to default-"none"
        */
        this.init = function () {
            var initOpt = opt.filterWrap.querySelector('[data-filtertype="all"]');
            util.addClass(initOpt, 'active');
            var text = initOpt.innerText.split(' ')[0];
            opt.filterWrap.querySelector('.filter-result').innerText = '筛选：无';
        },

        /*
        * shoot: when a filter is clicked.
        * add filter color and text to selected one.
        */
        this.filterSelect = function (e) {
            var oldEle = opt.filterWrap.querySelector('.active');
            var newEle = e.target;
            util.removeClass(oldEle, 'active');
            util.addClass(e.target, 'active');
            var text = newEle.innerText.replace(newEle.querySelector('.filter-num').innerText, '');
            if (text === '查看全部') {
                text = '无';
            }
            opt.filterWrap.querySelector('.filter-result').innerText = '筛选：' + text;
            // in wise, when select, collapse filter
            if (window.innerWidth <= opt.mobileWidth) {
                _this.toggleFilter();
            }
            _this.applyFilter(newEle.dataset.filtertype);
        },

        /*
        * shoot: on mobile when filter btn is clicked.
        * slide up or down the whole filter.
        */
        this.toggleFilter = function () {
            var listWrap = opt.filterWrap.querySelector('.filter-list');

            // hide and show filter list only on wise
            if (window.innerWidth <= opt.mobileWidth) {

                if (util.hasClass(listWrap, 'show')) {
                    // hide filter list
                    listWrap.style.height = '0px';
                } else {
                    // show filter list
                    listWrap.style.transition = 'none';
                    listWrap.style.WebkitTransition = 'none';
                    listWrap.style.height = 'auto';
                    var height = getComputedStyle(listWrap).height;

                    // target height acquired, now start the animation
                    listWrap.style.height = '0px';
                    listWrap.style.transition = 'height 0.3s';
                    setTimeout(function () {
                        // trick: in setTimeout, or there won't be any animation
                        listWrap.style.height = height;
                    }, 10);
                }
                util.toggleClass(listWrap, 'show');
            }
        },

        /*
        * shoot: when filter btn is clicked.
        * hide items that cant pass the filter.
        */
        this.applyFilter = function (filter) {
            var num = 0;
            opt.itemWrap.querySelectorAll('.filter-item').forEach(function (item) {
                if (item.dataset.filtertype.match(util.containReg(filter)) || filter === 'all') {
                    // show item
                    num ++;
                    item.style.display = 'block';
                } else {
                    // hide item
                    item.style.display = 'none';
                }
            });
            if (!num) {
                // no item can be shown, add "no item" text
                if (!opt.itemWrap.querySelector('.filter-emptytip')) {
                    var emptyTip = document.createElement('div');
                    util.addClass(emptyTip, 'filter-emptytip');
                    emptyTip.innerHTML = opt.emptyTip;
                    opt.itemWrap.appendChild(emptyTip);
                } else {}
            } else {
                var emptyTip = opt.itemWrap.querySelector('.filter-emptytip');
                if (emptyTip) {
                    opt.itemWrap.removeChild(emptyTip);
                }
            }
            window.scrollTo(0,0);
        }

        /*
        * add click event to all filters
        * when clicked, select the filter,
        * if wise, collapse filter list.
        */
        opt.filterWrap.querySelectorAll('a').forEach(function(ele) {
            ele.addEventListener('click', _this.filterSelect);
        });

        /*
        * add click event to filter result, which show only on wise.
        * when clicked, uncollapse and collapse filter list.
        */
        opt.filterWrap.querySelector('.filter-result').addEventListener('click', _this.toggleFilter);
    }

    // 初始化
    customElement.prototype.init = function () {
        this.build = build;
    };
    return customElement;
});

require(['mip-mipengine-filter'], function (filter) {
    // 引入组件需要的css文件，选填
    MIP.css.mipMipengineFilter += __inline('./mip-mipengine-filter.less');
    //注册组件
    MIP.registerMipElement('mip-mipengine-filter', filter, MIP.css.mipMipengineFilter);
});<|endoftext|>