<commit_before>/* ***** BEGIN LICENSE BLOCK *****
 *   Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is WebTabs.
 *
 * The Initial Developer of the Original Code is
 * David Ascher.
 * Portions created by the Initial Developer are Copyright (C) 2010
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Dave Townsend <dtownsend@oxymoronical.com>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/NetUtil.jsm");
Components.utils.import("resource://webapptabs/modules/LogManager.jsm");
LogManager.createLogger(this, "webtab");
Components.utils.import("resource://webapptabs/modules/ConfigManager.jsm");

const Cc = Components.classes;
const Ci = Components.interfaces;
const Ce = Components.Exception;
const Cr = Components.results;

const webtabs = {
  // A WeakMap from webapp to tab
  webappTabMap: null,
  // The UI element that contains the webapp buttons
  buttonContainer: null,

  // A reference to the default window content area click handler
  _origContentAreaClick: null,

  onLoad: function() {
    this.webappTabMap = new WeakMap();
    this.buttonContainer = document.getElementById("webapptabs-buttons");

    ConfigManager.webappList.forEach(function(aDesc) {
      this.createWebAppButton(aDesc);
    }, this);

    ConfigManager.addChangeListener(this.configChanged);

    this._origContentAreaClick = contentAreaClick;
    window.contentAreaClick = this.newContentAreaClick;
  },

  onUnload: function() {
    window.contentAreaClick = this._origContentAreaClick;

    ConfigManager.removeChangeListener(this.configChanged);

    ConfigManager.webappList.forEach(function(aDesc) {
      this.removeWebAppButton(aDesc);
    }, this);
  },

  // Called without a proper this
  configChanged: function() {
    webtabs.updateWebAppButtons();
  },

  updateWebAppButtons: function() {
    let before = this.buttonContainer.firstChild;

    // Loop through all webapps, for each either move it to the current position
    // or create it
    ConfigManager.webappList.forEach(function(aDesc) {
      if (before) {
        // Common case is the button will be the next in the list
        if (aDesc.id == before.id) {
          before = before.nextSibling;
          return;
        }

        let found = before.nextSibling;
        while (found && found.id != aDesc.id)
          found = found.nextSibling;
        if (found) {
          found.parentNode.insertBefore(found, before);
          return;
        }
      }

      // Webapp doesn't exist, create it
      this.createWebAppButton(aDesc);
    }, this);

    // Remove any remaining buttons
    while (before) {
      let next = before.nextSibling;
      this.removeWebAppButton(before.desc);
      before = next;
    }
  },

  newContentAreaClick: function(aEvent) {
    // If you click in a link to a website we have a shortcut for, we load it in a tab
    let href = hRefForClickEvent(aEvent);
    for (let [, tabDesc] in Iterator(ConfigManager.webappList)) {
      if (href.indexOf(tabDesc['options']['contentPage']) == 0) {
        tabDesc.options.contentPage = href;
        let tabmail = document.getElementById('tabmail');
        let info = tabmail.openTab("contentTab", tabDesc.options);
        info.tabNode.image=tabDesc.icon;
        aEvent.preventDefault()
        return;
      }
    }
    this._origContentAreaClick(aEvent);
  },

  createWebAppButton: function(aDesc, aBefore) {
    let button = document.createElement("toolbarbutton");
    button.setAttribute("id", aDesc.id);
    button.setAttribute("class", "webtab");
    button.setAttribute("image", aDesc.icon);
    button.setAttribute("tooltiptext", aDesc.name);
    this.buttonContainer.insertBefore(button, aBefore);
    button.desc = aDesc;

    button.addEventListener("command", function() {
      try {
        webtabs.openTab(aDesc);
      }
      catch (e) {
        ERROR("Failed to open webapp", e);
      }
    }, false);
  },

  removeWebAppButton: function(aDesc) {
    let info = this.getTabInfoForWebApp(aDesc);
    if (info)
      document.getElementById('tabmail').closeTab(info, true);

    let button = document.getElementById(aDesc.id);
    if (button)
      button.parentNode.removeChild(button);
    else
      ERROR("Missing webapp button for " + aDesc.name);
  },

  getTabInfoForWebApp: function(aDesc) {
    if (!this.webappTabMap.has(aDesc))
      return null;

    let info = this.webappTabMap.get(aDesc);
    if (!info)
      return null;

    // Check that this tabinfo is still in the UI
    let node = info.browser;
    while (node && node != node.ownerDocument.documentElement)
      node = node.parentNode;

    if (node)
      return info;
    return null;
  },

  openTab: function(aDesc) {
    let tabmail = document.getElementById('tabmail');

    let info = this.getTabInfoForWebApp(aDesc);
    if (info) {
      tabmail.switchToTab(info);
      return;
    }

    let url = NetUtil.newURI(aDesc.href);
    let regex = new RegExp("^http[s]?://" + url.hostname + "/");

    info = tabmail.openTab("contentTab", {
      contentPage: aDesc.href,
      clickHandler: "return true;"
    });

    this.webappTabMap.set(aDesc, info);

    info.browser.addEventListener("click", function(aEvent) {
      specialTabs.siteClickHandler(aEvent, regex);
    }, false);

    let listener = {
      onStateChange: function(aWebProgress, aRequest, aState, aStatus) {
        if ((aState & Ci.nsIWebProgressListener.STATE_IS_REQUEST) &&
            (aState & Ci.nsIWebProgressListener.STATE_STOP)) {
          let icon = info.tabNode.getAttribute("image");
          if (!icon)
            return;

          info.browser.removeProgressListener(listener);

          aDesc.icon = icon;
          ConfigManager.persistPrefs();

          let button = document.getElementById(aDesc.id);
          button.setAttribute("image", aDesc.icon);
        }
      },

      onLocationChange: function() { },
      onProgressChange: function() { },
      onSecurityChange: function() { },
      onStatusChange: function() { },
      QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
                                             Ci.nsISupportsWeakReference])
    };

    info.browser.addProgressListener(listener);
  },
};

var OverlayListener = {
  load: function() {
    webtabs.onLoad();
  },

  unload: function() {
    webtabs.onUnload();
  }
};
<commit_msg>Don't keep a fixed mapping between webapp and tab, instead find the tab for a webapp using a URL matching mechanism. Closes #17<commit_after>/* ***** BEGIN LICENSE BLOCK *****
 *   Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is WebTabs.
 *
 * The Initial Developer of the Original Code is
 * David Ascher.
 * Portions created by the Initial Developer are Copyright (C) 2010
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Dave Townsend <dtownsend@oxymoronical.com>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/NetUtil.jsm");
Components.utils.import("resource://webapptabs/modules/LogManager.jsm");
LogManager.createLogger(this, "webtab");
Components.utils.import("resource://webapptabs/modules/ConfigManager.jsm");

const Cc = Components.classes;
const Ci = Components.interfaces;
const Ce = Components.Exception;
const Cr = Components.results;

const webtabs = {
  // The UI element that contains the webapp buttons
  buttonContainer: null,

  // A reference to the default window content area click handler
  _origContentAreaClick: null,

  onLoad: function() {
    this.buttonContainer = document.getElementById("webapptabs-buttons");

    ConfigManager.webappList.forEach(function(aDesc) {
      this.createWebAppButton(aDesc);
    }, this);

    ConfigManager.addChangeListener(this.configChanged);

    this._origContentAreaClick = contentAreaClick;
    window.contentAreaClick = this.newContentAreaClick;
  },

  onUnload: function() {
    window.contentAreaClick = this._origContentAreaClick;

    ConfigManager.removeChangeListener(this.configChanged);

    ConfigManager.webappList.forEach(function(aDesc) {
      this.removeWebAppButton(aDesc);
    }, this);
  },

  // Called without a proper this
  configChanged: function() {
    webtabs.updateWebAppButtons();
  },

  updateWebAppButtons: function() {
    let before = this.buttonContainer.firstChild;

    // Loop through all webapps, for each either move it to the current position
    // or create it
    ConfigManager.webappList.forEach(function(aDesc) {
      if (before) {
        // Common case is the button will be the next in the list
        if (aDesc.id == before.id) {
          before = before.nextSibling;
          return;
        }

        let found = before.nextSibling;
        while (found && found.id != aDesc.id)
          found = found.nextSibling;
        if (found) {
          found.parentNode.insertBefore(found, before);
          return;
        }
      }

      // Webapp doesn't exist, create it
      this.createWebAppButton(aDesc);
    }, this);

    // Remove any remaining buttons
    while (before) {
      let next = before.nextSibling;
      this.removeWebAppButton(before.desc);
      before = next;
    }
  },

  newContentAreaClick: function(aEvent) {
    // If you click in a link to a website we have a shortcut for, we load it in a tab
    let href = hRefForClickEvent(aEvent);
    for (let [, tabDesc] in Iterator(ConfigManager.webappList)) {
      if (href.indexOf(tabDesc['options']['contentPage']) == 0) {
        tabDesc.options.contentPage = href;
        let tabmail = document.getElementById('tabmail');
        let info = tabmail.openTab("contentTab", tabDesc.options);
        info.tabNode.image=tabDesc.icon;
        aEvent.preventDefault()
        return;
      }
    }
    this._origContentAreaClick(aEvent);
  },

  createWebAppButton: function(aDesc, aBefore) {
    let button = document.createElement("toolbarbutton");
    button.setAttribute("id", aDesc.id);
    button.setAttribute("class", "webtab");
    button.setAttribute("image", aDesc.icon);
    button.setAttribute("tooltiptext", aDesc.name);
    this.buttonContainer.insertBefore(button, aBefore);
    button.desc = aDesc;

    button.addEventListener("command", function() {
      try {
        webtabs.openTab(aDesc);
      }
      catch (e) {
        ERROR("Failed to open webapp", e);
      }
    }, false);
  },

  removeWebAppButton: function(aDesc) {
    let button = document.getElementById(aDesc.id);
    if (button)
      button.parentNode.removeChild(button);
    else
      ERROR("Missing webapp button for " + aDesc.name);
  },

  isURLForWebApp: function(aURL, aDesc) {
    return aURL.substring(0, aDesc.href.length) == aDesc.href;
  },

  getTabInfoForWebApp: function(aDesc) {
    let tabmail = document.getElementById('tabmail');

    let tabs = tabmail.tabInfo.filter(function(aTabInfo) {
      if (!("browser" in aTabInfo))
        return false;

      return this.isURLForWebApp(aTabInfo.browser.currentURI.spec, aDesc);
    }, this);

    if (tabs.length > 0)
      return tabs[0];

    return null;
  },

  openTab: function(aDesc) {
    let tabmail = document.getElementById('tabmail');

    let info = this.getTabInfoForWebApp(aDesc);
    if (info) {
      tabmail.switchToTab(info);
      return;
    }

    let url = NetUtil.newURI(aDesc.href);
    let regex = new RegExp("^http[s]?://" + url.hostname + "/");

    info = tabmail.openTab("contentTab", {
      contentPage: aDesc.href,
      clickHandler: "return true;"
    });

    info.browser.addEventListener("click", function(aEvent) {
      specialTabs.siteClickHandler(aEvent, regex);
    }, false);

    let listener = {
      onStateChange: function(aWebProgress, aRequest, aState, aStatus) {
        if ((aState & Ci.nsIWebProgressListener.STATE_IS_REQUEST) &&
            (aState & Ci.nsIWebProgressListener.STATE_STOP)) {
          let icon = info.tabNode.getAttribute("image");
          if (!icon)
            return;

          info.browser.removeProgressListener(listener);

          aDesc.icon = icon;
          ConfigManager.persistPrefs();

          let button = document.getElementById(aDesc.id);
          button.setAttribute("image", aDesc.icon);
        }
      },

      onLocationChange: function() { },
      onProgressChange: function() { },
      onSecurityChange: function() { },
      onStatusChange: function() { },
      QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
                                             Ci.nsISupportsWeakReference])
    };

    info.browser.addProgressListener(listener);
  },
};

var OverlayListener = {
  load: function() {
    webtabs.onLoad();
  },

  unload: function() {
    webtabs.onUnload();
  }
};
<|endoftext|>


<commit_before>/* ***** BEGIN LICENSE BLOCK *****
 *   Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is WebTabs.
 *
 * The Initial Developer of the Original Code is
 * David Ascher.
 * Portions created by the Initial Developer are Copyright (C) 2010
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Dave Townsend <dtownsend@oxymoronical.com>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/NetUtil.jsm");
Components.utils.import("resource://webapptabs/modules/LogManager.jsm");
LogManager.createLogger(this, "webtab");
Components.utils.import("resource://webapptabs/modules/ConfigManager.jsm");

const Cc = Components.classes;
const Ci = Components.interfaces;
const Ce = Components.Exception;
const Cr = Components.results;

const webtabs = {
  // The UI element that contains the webapp buttons
  buttonContainer: null,

  onLoad: function() {
    this.buttonContainer = document.getElementById("webapptabs-buttons");

    ConfigManager.webappList.forEach(function(aDesc) {
      this.createWebAppButton(aDesc);
    }, this);

    ConfigManager.addChangeListener(this.configChanged);

    let container = document.getElementById("tabpanelcontainer");
    container.addEventListener("click", this, true);

    document.getElementById("mailContext").addEventListener("popupshowing", this, false);
  },

  onUnload: function() {
    document.getElementById("mailContext").removeEventListener("popupshowing", this, false);

    let container = document.getElementById("tabpanelcontainer");
    container.removeEventListener("click", this, true);

    ConfigManager.removeChangeListener(this.configChanged);

    ConfigManager.webappList.forEach(function(aDesc) {
      this.removeWebAppButton(aDesc);
    }, this);
  },

  // Called without a proper this
  configChanged: function() {
    webtabs.updateWebAppButtons();
  },

  updateWebAppButtons: function() {
    let before = this.buttonContainer.firstChild;

    // Loop through all webapps, for each either move it to the current position
    // or create it
    ConfigManager.webappList.forEach(function(aDesc) {
      if (before) {
        // Common case is the button will be the next in the list
        if (aDesc.id == before.id) {
          before = before.nextSibling;
          return;
        }

        let found = before.nextSibling;
        while (found && found.id != aDesc.id)
          found = found.nextSibling;
        if (found) {
          found.parentNode.insertBefore(found, before);
          return;
        }
      }

      // Webapp doesn't exist, create it
      this.createWebAppButton(aDesc);
    }, this);

    // Remove any remaining buttons
    while (before) {
      let next = before.nextSibling;
      this.removeWebAppButton(before.desc);
      before = next;
    }
  },

  createWebAppButton: function(aDesc, aBefore) {
    let button = document.createElement("toolbarbutton");
    button.setAttribute("id", aDesc.id);
    button.setAttribute("class", "webtab");
    button.setAttribute("image", aDesc.icon);
    button.setAttribute("tooltiptext", aDesc.name);
    this.buttonContainer.insertBefore(button, aBefore);
    button.desc = aDesc;

    button.addEventListener("command", function() {
      try {
        webtabs.openTab(aDesc);
      }
      catch (e) {
        ERROR("Failed to open webapp", e);
      }
    }, false);
  },

  removeWebAppButton: function(aDesc) {
    let button = document.getElementById(aDesc.id);
    if (button)
      button.parentNode.removeChild(button);
    else
      ERROR("Missing webapp button for " + aDesc.name);
  },

  isURLForWebApp: function(aURL, aDesc) {
    return aURL.substring(0, aDesc.href.length) == aDesc.href;
  },

  getWebAppForURL: function(aURL) {
    let descs = ConfigManager.webappList.filter(this.isURLForWebApp.bind(this, aURL));
    if (descs.length > 0)
      return descs[0];
    return null;
  },

  getTabInfoForWebApp: function(aDesc) {
    let tabmail = document.getElementById('tabmail');

    let tabs = tabmail.tabInfo.filter(function(aTabInfo) {
      if (!("browser" in aTabInfo))
        return false;

      return this.isURLForWebApp(aTabInfo.browser.currentURI.spec, aDesc);
    }, this);

    if (tabs.length > 0)
      return tabs[0];

    return null;
  },

  openTab: function(aDesc, aURL) {
    let tabmail = document.getElementById('tabmail');

    let info = this.getTabInfoForWebApp(aDesc);
    if (info) {
      tabmail.switchToTab(info);
      return;
    }

    info = tabmail.openTab("contentTab", {
      contentPage: aURL ? aURL : aDesc.href
    });

    // Only get new favicons when loading the normal webapp url
    if (aURL)
      return;

    let listener = {
      onStateChange: function(aWebProgress, aRequest, aState, aStatus) {
        if ((aState & Ci.nsIWebProgressListener.STATE_IS_REQUEST) &&
            (aState & Ci.nsIWebProgressListener.STATE_STOP)) {
          let icon = info.tabNode.getAttribute("image");
          if (!icon)
            return;

          info.browser.removeProgressListener(listener);

          aDesc.icon = icon;
          ConfigManager.persistPrefs();

          let button = document.getElementById(aDesc.id);
          button.setAttribute("image", aDesc.icon);
        }
      },

      onLocationChange: function() { },
      onProgressChange: function() { },
      onSecurityChange: function() { },
      onStatusChange: function() { },
      QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
                                             Ci.nsISupportsWeakReference])
    };

    info.browser.addProgressListener(listener);
  },

  onPopupShowing: function(aEvent) {
    let target = document.popupNode;

    // If the context menu already detected the area as editable then bail out
    if (gContextMenu.onEditableArea)
      return;

    let win = target.ownerDocument.defaultView;
    if (!win)
      return;

    try {
      var editingSession = win.QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsIWebNavigation)
                              .QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsIEditingSession);
      if (!editingSession.windowIsEditable(win))
        return;
      if (win.getComputedStyle(target, "").getPropertyValue("-moz-user-modify") != "read-write")
        return;
    }
    catch(ex) {
      // If someone built with composer disabled, we can't get an editing session.
      return;
    }

    gContextMenu.onTextInput = true;
    gContextMenu.onEditableArea = true;
    gSpellChecker.init(editingSession.getEditorForWindow(win));
    gSpellChecker.initFromEvent(document.popupRangeParent, document.popupRangeOffset);
    gContextMenu.initSpellingItems();
  },

  onContentClick: function(aEvent) {
    let info = document.getElementById('tabmail').currentTabInfo;
    if (!info)
      return;

    // Don't handle events that: a) aren't trusted, b) have already been
    // handled or c) aren't left-click.
    if (!aEvent.isTrusted || aEvent.getPreventDefault() || aEvent.button)
      return;

    let href = hRefForClickEvent(aEvent, true);
    if (!href)
      return;

    LOG("Saw url " + href);

    // If this URL matches a webapp then switch to or open that
    let newDesc = this.getWebAppForURL(href);
    if (newDesc) {
      aEvent.preventDefault();
      aEvent.stopPropagation();

      let newInfo = this.getTabInfoForWebApp(newDesc);
      if (newInfo) {
        let tabmail = document.getElementById('tabmail');
        tabmail.switchToTab(newInfo);
        newInfo.browser.loadURI(href, null, null);
      }
      else {
        this.openTab(newDesc, href);
      }
      return;
    }

    let protocolSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
                      getService(Ci.nsIExternalProtocolService);

    let uri = makeURI(href);
    if (!protocolSvc.isExposedProtocol(uri.scheme) ||
        uri.schemeIs("http") || uri.schemeIs("https") ||
        uri.schemeIs("about")) {
      aEvent.preventDefault();
      aEvent.stopPropagation();
      openLinkExternally(href);
    }
  },

  handleEvent: function(aEvent) {
    try {
      switch (aEvent.type) {
      case "popupshowing":
        this.onPopupShowing(aEvent);
        break;
      case "click":
        this.onContentClick(aEvent);
        break;
      }
    }
    catch (e) {
      ERROR("Exception during " + aEvent.type + " event", e);
    }
  },
};

var OverlayListener = {
  load: function() {
    webtabs.onLoad();
  },

  unload: function() {
    webtabs.onUnload();
  }
};
<commit_msg>Allow events that are likely to load the existing webapp to continue as normal to allow JS to run. Closes #20<commit_after>/* ***** BEGIN LICENSE BLOCK *****
 *   Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is WebTabs.
 *
 * The Initial Developer of the Original Code is
 * David Ascher.
 * Portions created by the Initial Developer are Copyright (C) 2010
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Dave Townsend <dtownsend@oxymoronical.com>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/NetUtil.jsm");
Components.utils.import("resource://webapptabs/modules/LogManager.jsm");
LogManager.createLogger(this, "webtab");
Components.utils.import("resource://webapptabs/modules/ConfigManager.jsm");

const Cc = Components.classes;
const Ci = Components.interfaces;
const Ce = Components.Exception;
const Cr = Components.results;

const webtabs = {
  // The UI element that contains the webapp buttons
  buttonContainer: null,

  onLoad: function() {
    this.buttonContainer = document.getElementById("webapptabs-buttons");

    ConfigManager.webappList.forEach(function(aDesc) {
      this.createWebAppButton(aDesc);
    }, this);

    ConfigManager.addChangeListener(this.configChanged);

    let container = document.getElementById("tabpanelcontainer");
    container.addEventListener("click", this, true);

    document.getElementById("mailContext").addEventListener("popupshowing", this, false);
  },

  onUnload: function() {
    document.getElementById("mailContext").removeEventListener("popupshowing", this, false);

    let container = document.getElementById("tabpanelcontainer");
    container.removeEventListener("click", this, true);

    ConfigManager.removeChangeListener(this.configChanged);

    ConfigManager.webappList.forEach(function(aDesc) {
      this.removeWebAppButton(aDesc);
    }, this);
  },

  // Called without a proper this
  configChanged: function() {
    webtabs.updateWebAppButtons();
  },

  updateWebAppButtons: function() {
    let before = this.buttonContainer.firstChild;

    // Loop through all webapps, for each either move it to the current position
    // or create it
    ConfigManager.webappList.forEach(function(aDesc) {
      if (before) {
        // Common case is the button will be the next in the list
        if (aDesc.id == before.id) {
          before = before.nextSibling;
          return;
        }

        let found = before.nextSibling;
        while (found && found.id != aDesc.id)
          found = found.nextSibling;
        if (found) {
          found.parentNode.insertBefore(found, before);
          return;
        }
      }

      // Webapp doesn't exist, create it
      this.createWebAppButton(aDesc);
    }, this);

    // Remove any remaining buttons
    while (before) {
      let next = before.nextSibling;
      this.removeWebAppButton(before.desc);
      before = next;
    }
  },

  createWebAppButton: function(aDesc, aBefore) {
    let button = document.createElement("toolbarbutton");
    button.setAttribute("id", aDesc.id);
    button.setAttribute("class", "webtab");
    button.setAttribute("image", aDesc.icon);
    button.setAttribute("tooltiptext", aDesc.name);
    this.buttonContainer.insertBefore(button, aBefore);
    button.desc = aDesc;

    button.addEventListener("command", function() {
      try {
        webtabs.openTab(aDesc);
      }
      catch (e) {
        ERROR("Failed to open webapp", e);
      }
    }, false);
  },

  removeWebAppButton: function(aDesc) {
    let button = document.getElementById(aDesc.id);
    if (button)
      button.parentNode.removeChild(button);
    else
      ERROR("Missing webapp button for " + aDesc.name);
  },

  isURLForWebApp: function(aURL, aDesc) {
    return aURL.substring(0, aDesc.href.length) == aDesc.href;
  },

  getWebAppForURL: function(aURL) {
    let descs = ConfigManager.webappList.filter(this.isURLForWebApp.bind(this, aURL));
    if (descs.length > 0)
      return descs[0];
    return null;
  },

  getTabInfoForWebApp: function(aDesc) {
    let tabmail = document.getElementById('tabmail');

    let tabs = tabmail.tabInfo.filter(function(aTabInfo) {
      if (!("browser" in aTabInfo))
        return false;

      return this.isURLForWebApp(aTabInfo.browser.currentURI.spec, aDesc);
    }, this);

    if (tabs.length > 0)
      return tabs[0];

    return null;
  },

  openTab: function(aDesc, aURL) {
    let tabmail = document.getElementById('tabmail');

    let info = this.getTabInfoForWebApp(aDesc);
    if (info) {
      tabmail.switchToTab(info);
      return;
    }

    info = tabmail.openTab("contentTab", {
      contentPage: aURL ? aURL : aDesc.href,
      clickHandler: "return true;"
    });

    // Only get new favicons when loading the normal webapp url
    if (aURL)
      return;

    let listener = {
      onStateChange: function(aWebProgress, aRequest, aState, aStatus) {
        if ((aState & Ci.nsIWebProgressListener.STATE_IS_REQUEST) &&
            (aState & Ci.nsIWebProgressListener.STATE_STOP)) {
          let icon = info.tabNode.getAttribute("image");
          if (!icon)
            return;

          info.browser.removeProgressListener(listener);

          aDesc.icon = icon;
          ConfigManager.persistPrefs();

          let button = document.getElementById(aDesc.id);
          button.setAttribute("image", aDesc.icon);
        }
      },

      onLocationChange: function() { },
      onProgressChange: function() { },
      onSecurityChange: function() { },
      onStatusChange: function() { },
      QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
                                             Ci.nsISupportsWeakReference])
    };

    info.browser.addProgressListener(listener);
  },

  onPopupShowing: function(aEvent) {
    let target = document.popupNode;

    // If the context menu already detected the area as editable then bail out
    if (gContextMenu.onEditableArea)
      return;

    let win = target.ownerDocument.defaultView;
    if (!win)
      return;

    try {
      var editingSession = win.QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsIWebNavigation)
                              .QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsIEditingSession);
      if (!editingSession.windowIsEditable(win))
        return;
      if (win.getComputedStyle(target, "").getPropertyValue("-moz-user-modify") != "read-write")
        return;
    }
    catch(ex) {
      // If someone built with composer disabled, we can't get an editing session.
      return;
    }

    gContextMenu.onTextInput = true;
    gContextMenu.onEditableArea = true;
    gSpellChecker.init(editingSession.getEditorForWindow(win));
    gSpellChecker.initFromEvent(document.popupRangeParent, document.popupRangeOffset);
    gContextMenu.initSpellingItems();
  },

  onContentClick: function(aEvent) {
    let info = document.getElementById('tabmail').currentTabInfo;
    if (!info)
      return;

    // Don't handle events that: a) aren't trusted, b) have already been
    // handled or c) aren't left-click.
    if (!aEvent.isTrusted || aEvent.getPreventDefault() || aEvent.button)
      return;

    let href = hRefForClickEvent(aEvent, true);
    if (!href)
      return;

    LOG("Saw url " + href);

    // If this URL matches a webapp then switch to or open that
    let newDesc = this.getWebAppForURL(href);
    if (newDesc) {
      if (newDesc == this.getWebAppForURL(info.browser.currentURI.spec)) {
        // If the load is for the same webapp that the tab is already displaying
        // then just allow the event to proceed as normal.
        return;
      }

      aEvent.preventDefault();
      aEvent.stopPropagation();

      let newInfo = this.getTabInfoForWebApp(newDesc);
      if (newInfo) {
        let tabmail = document.getElementById('tabmail');
        tabmail.switchToTab(newInfo);
        newInfo.browser.loadURI(href, null, null);
      }
      else {
        this.openTab(newDesc, href);
      }
      return;
    }

    let protocolSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
                      getService(Ci.nsIExternalProtocolService);

    let uri = makeURI(href);
    if (!protocolSvc.isExposedProtocol(uri.scheme) ||
        uri.schemeIs("http") || uri.schemeIs("https") ||
        uri.schemeIs("about")) {
      aEvent.preventDefault();
      aEvent.stopPropagation();
      openLinkExternally(href);
    }
  },

  handleEvent: function(aEvent) {
    try {
      switch (aEvent.type) {
      case "popupshowing":
        this.onPopupShowing(aEvent);
        break;
      case "click":
        this.onContentClick(aEvent);
        break;
      }
    }
    catch (e) {
      ERROR("Exception during " + aEvent.type + " event", e);
    }
  },
};

var OverlayListener = {
  load: function() {
    webtabs.onLoad();
  },

  unload: function() {
    webtabs.onUnload();
  }
};
<|endoftext|>


<commit_before>/**
 * @author mrdoob / http://mrdoob.com/
 */

( function ( THREE ) {

	THREE.Raycaster = function ( origin, direction, near, far ) {

		this.ray = new THREE.Ray( origin, direction );
		this.near = near || 0;
		this.far = far || Infinity;

	};

	var sphere = new THREE.Sphere();

	var originCopy = new THREE.Vector3();

	var localOriginCopy = new THREE.Vector3();
	var localDirectionCopy = new THREE.Vector3();

	var vector = new THREE.Vector3();
	var normal = new THREE.Vector3();
	var intersectPoint = new THREE.Vector3();

	var inverseMatrix = new THREE.Matrix4();

	var descSort = function ( a, b ) {

		return a.distance - b.distance;

	};

	var v0 = new THREE.Vector3(), v1 = new THREE.Vector3(), v2 = new THREE.Vector3();

	// http://www.blackpawn.com/texts/pointinpoly/default.html

	var pointInFace3 = function ( p, a, b, c ) {

		v0.sub( c, a );
		v1.sub( b, a );
		v2.sub( p, a );

		var dot00 = v0.dot( v0 );
		var dot01 = v0.dot( v1 );
		var dot02 = v0.dot( v2 );
		var dot11 = v1.dot( v1 );
		var dot12 = v1.dot( v2 );

		var invDenom = 1 / ( dot00 * dot11 - dot01 * dot01 );
		var u = ( dot11 * dot02 - dot01 * dot12 ) * invDenom;
		var v = ( dot00 * dot12 - dot01 * dot02 ) * invDenom;

		return ( u >= 0 ) && ( v >= 0 ) && ( u + v < 1 );

	};

	var intersectObject = function ( object, raycaster, intersects ) {

		if ( object instanceof THREE.Particle ) {

			var distance = raycaster.ray.distanceToPoint( object.matrixWorld.getPosition() );

			if ( distance > object.scale.x ) {

				return intersects;

			}

			intersects.push( {

				distance: distance,
				point: object.position,
				face: null,
				object: object

			} );

		} else if ( object instanceof THREE.Mesh ) {

			// Checking boundingSphere distance to ray
			sphere.set(
				object.matrixWorld.getPosition(),
				object.geometry.boundingSphere.radius* object.matrixWorld.getMaxScaleOnAxis() );

			if ( ! raycaster.ray.isIntersectionSphere( sphere ) ) {

				return intersects;

			}

			// Checking faces

			var geometry = object.geometry;
			var vertices = geometry.vertices;

			var isFaceMaterial = object.material instanceof THREE.MeshFaceMaterial;
			var objectMaterials = isFaceMaterial === true ? object.material.materials : null;

			var side = object.material.side;

			var a, b, c, d;
			var precision = raycaster.precision;

			object.matrixRotationWorld.extractRotation( object.matrixWorld );

			originCopy.copy( raycaster.ray.origin );

			inverseMatrix.getInverse( object.matrixWorld );

			localOriginCopy.copy( originCopy );
			inverseMatrix.multiplyVector3( localOriginCopy );

			localDirectionCopy.copy( raycaster.ray.direction );
			inverseMatrix.rotateAxis( localDirectionCopy ).normalize();

			for ( var f = 0, fl = geometry.faces.length; f < fl; f ++ ) {

				var face = geometry.faces[ f ];

				var material = isFaceMaterial === true ? objectMaterials[ face.materialIndex ] : object.material;

				if ( material === undefined ) continue;

				side = material.side;

				vector.sub( face.centroid, localOriginCopy );

				var normal = face.normal;
				var dot = localDirectionCopy.dot( normal );

				// bail if raycaster and plane are parallel

				if ( Math.abs( dot ) < precision ) continue;

				// calc distance to plane

				var scalar = normal.dot( vector ) / dot;

				// if negative distance, then plane is behind raycaster

				if ( scalar < 0 ) continue;

				if ( side === THREE.DoubleSide || ( side === THREE.FrontSide ? dot < 0 : dot > 0 ) ) {

					intersectPoint.add( localOriginCopy, localDirectionCopy.multiplyScalar( scalar ) );

					if ( face instanceof THREE.Face3 ) {

						a = vertices[ face.a ];
						b = vertices[ face.b ];
						c = vertices[ face.c ];

						if ( pointInFace3( intersectPoint, a, b, c ) ) {

							var point = object.matrixWorld.multiplyVector3( intersectPoint.clone() );
							distance = originCopy.distanceTo( point );

							if ( distance < raycaster.near || distance > raycaster.far ) continue;

							intersects.push( {

								distance: distance,
								point: point,
								face: face,
								faceIndex: f,
								object: object

							} );

						}

					} else if ( face instanceof THREE.Face4 ) {

						a = vertices[ face.a ];
						b = vertices[ face.b ];
						c = vertices[ face.c ];
						d = vertices[ face.d ];

						if ( pointInFace3( intersectPoint, a, b, d ) || pointInFace3( intersectPoint, b, c, d ) ) {

							var point = object.matrixWorld.multiplyVector3( intersectPoint.clone() );
							distance = originCopy.distanceTo( point );

							if ( distance < raycaster.near || distance > raycaster.far ) continue;

							intersects.push( {

								distance: distance,
								point: point,
								face: face,
								faceIndex: f,
								object: object

							} );

						}

					}

				}

			}

		}

	};

	var intersectDescendants = function ( object, raycaster, intersects ) {

		var descendants = object.getDescendants();

		for ( var i = 0, l = descendants.length; i < l; i ++ ) {

			intersectObject( descendants[ i ], raycaster, intersects );

		}
	};

	//

	THREE.Raycaster.prototype.precision = 0.0001;

	THREE.Raycaster.prototype.set = function ( origin, direction ) {

		this.ray.set( origin, direction );

	};

	THREE.Raycaster.prototype.intersectObject = function ( object, recursive ) {

		var intersects = [];

		if ( recursive === true ) {

			intersectDescendants( object, this, intersects );

		}

		intersectObject( object, this, intersects );

		intersects.sort( descSort );

		return intersects;

	};

	THREE.Raycaster.prototype.intersectObjects = function ( objects, recursive ) {

		var intersects = [];

		for ( var i = 0, l = objects.length; i < l; i ++ ) {

			intersectObject( objects[ i ], this, intersects );

			if ( recursive === true ) {

				intersectDescendants( objects[ i ], this, intersects );

			}
		}

		intersects.sort( descSort );

		return intersects;

	};

}( THREE ) );
<commit_msg>replace Vector3-based localOriginCopy, localDirectionCopy with Ray-based localRay<commit_after>/**
 * @author mrdoob / http://mrdoob.com/
 */

( function ( THREE ) {

	THREE.Raycaster = function ( origin, direction, near, far ) {

		this.ray = new THREE.Ray( origin, direction );
		this.near = near || 0;
		this.far = far || Infinity;

	};

	var sphere = new THREE.Sphere();

	var originCopy = new THREE.Vector3();

	var localRay = new THREE.Ray();

	var vector = new THREE.Vector3();
	var normal = new THREE.Vector3();
	var intersectPoint = new THREE.Vector3();

	var inverseMatrix = new THREE.Matrix4();

	var descSort = function ( a, b ) {

		return a.distance - b.distance;

	};

	var v0 = new THREE.Vector3(), v1 = new THREE.Vector3(), v2 = new THREE.Vector3();

	// http://www.blackpawn.com/texts/pointinpoly/default.html

	var pointInFace3 = function ( p, a, b, c ) {

		v0.sub( c, a );
		v1.sub( b, a );
		v2.sub( p, a );

		var dot00 = v0.dot( v0 );
		var dot01 = v0.dot( v1 );
		var dot02 = v0.dot( v2 );
		var dot11 = v1.dot( v1 );
		var dot12 = v1.dot( v2 );

		var invDenom = 1 / ( dot00 * dot11 - dot01 * dot01 );
		var u = ( dot11 * dot02 - dot01 * dot12 ) * invDenom;
		var v = ( dot00 * dot12 - dot01 * dot02 ) * invDenom;

		return ( u >= 0 ) && ( v >= 0 ) && ( u + v < 1 );

	};

	var intersectObject = function ( object, raycaster, intersects ) {

		if ( object instanceof THREE.Particle ) {

			var distance = raycaster.ray.distanceToPoint( object.matrixWorld.getPosition() );

			if ( distance > object.scale.x ) {

				return intersects;

			}

			intersects.push( {

				distance: distance,
				point: object.position,
				face: null,
				object: object

			} );

		} else if ( object instanceof THREE.Mesh ) {

			// Checking boundingSphere distance to ray
			sphere.set(
				object.matrixWorld.getPosition(),
				object.geometry.boundingSphere.radius* object.matrixWorld.getMaxScaleOnAxis() );

			if ( ! raycaster.ray.isIntersectionSphere( sphere ) ) {

				return intersects;

			}

			// Checking faces

			var geometry = object.geometry;
			var vertices = geometry.vertices;

			var isFaceMaterial = object.material instanceof THREE.MeshFaceMaterial;
			var objectMaterials = isFaceMaterial === true ? object.material.materials : null;

			var side = object.material.side;

			var a, b, c, d;
			var precision = raycaster.precision;

			object.matrixRotationWorld.extractRotation( object.matrixWorld );

			originCopy.copy( raycaster.ray.origin );

			inverseMatrix.getInverse( object.matrixWorld );

			localRay.copy( raycaster.ray ).transformSelf( inverseMatrix );

			for ( var f = 0, fl = geometry.faces.length; f < fl; f ++ ) {

				var face = geometry.faces[ f ];

				var material = isFaceMaterial === true ? objectMaterials[ face.materialIndex ] : object.material;

				if ( material === undefined ) continue;

				side = material.side;

				vector.sub( face.centroid, localRay.origin );

				var normal = face.normal;
				var dot = localRay.direction.dot( normal );

				// bail if raycaster and plane are parallel

				if ( Math.abs( dot ) < precision ) continue;

				// calc distance to plane

				var scalar = normal.dot( vector ) / dot;

				// if negative distance, then plane is behind raycaster

				if ( scalar < 0 ) continue;

				if ( side === THREE.DoubleSide || ( side === THREE.FrontSide ? dot < 0 : dot > 0 ) ) {

					intersectPoint.add( localRay.origin, localRay.direction.multiplyScalar( scalar ) );

					if ( face instanceof THREE.Face3 ) {

						a = vertices[ face.a ];
						b = vertices[ face.b ];
						c = vertices[ face.c ];

						if ( pointInFace3( intersectPoint, a, b, c ) ) {

							var point = object.matrixWorld.multiplyVector3( intersectPoint.clone() );
							distance = originCopy.distanceTo( point );

							if ( distance < raycaster.near || distance > raycaster.far ) continue;

							intersects.push( {

								distance: distance,
								point: point,
								face: face,
								faceIndex: f,
								object: object

							} );

						}

					} else if ( face instanceof THREE.Face4 ) {

						a = vertices[ face.a ];
						b = vertices[ face.b ];
						c = vertices[ face.c ];
						d = vertices[ face.d ];

						if ( pointInFace3( intersectPoint, a, b, d ) || pointInFace3( intersectPoint, b, c, d ) ) {

							var point = object.matrixWorld.multiplyVector3( intersectPoint.clone() );
							distance = originCopy.distanceTo( point );

							if ( distance < raycaster.near || distance > raycaster.far ) continue;

							intersects.push( {

								distance: distance,
								point: point,
								face: face,
								faceIndex: f,
								object: object

							} );

						}

					}

				}

			}

		}

	};

	var intersectDescendants = function ( object, raycaster, intersects ) {

		var descendants = object.getDescendants();

		for ( var i = 0, l = descendants.length; i < l; i ++ ) {

			intersectObject( descendants[ i ], raycaster, intersects );

		}
	};

	//

	THREE.Raycaster.prototype.precision = 0.0001;

	THREE.Raycaster.prototype.set = function ( origin, direction ) {

		this.ray.set( origin, direction );

	};

	THREE.Raycaster.prototype.intersectObject = function ( object, recursive ) {

		var intersects = [];

		if ( recursive === true ) {

			intersectDescendants( object, this, intersects );

		}

		intersectObject( object, this, intersects );

		intersects.sort( descSort );

		return intersects;

	};

	THREE.Raycaster.prototype.intersectObjects = function ( objects, recursive ) {

		var intersects = [];

		for ( var i = 0, l = objects.length; i < l; i ++ ) {

			intersectObject( objects[ i ], this, intersects );

			if ( recursive === true ) {

				intersectDescendants( objects[ i ], this, intersects );

			}
		}

		intersects.sort( descSort );

		return intersects;

	};

}( THREE ) );
<|endoftext|>


<commit_before>/**
 * @author mrdoob / http://mrdoob.com/
 * @author bhouston / http://exocortex.com/
 * @author stephomi / http://stephaneginier.com/
 */

( function ( THREE ) {

	THREE.Raycaster = function ( origin, direction, near, far ) {

		this.ray = new THREE.Ray( origin, direction );
		// direction is assumed to be normalized (for accurate distance calculations)

		this.near = near || 0;
		this.far = far || Infinity;

	};

	var sphere = new THREE.Sphere();
	var localRay = new THREE.Ray();
	var facePlane = new THREE.Plane();
	var intersectPoint = new THREE.Vector3();
	var matrixPosition = new THREE.Vector3();

	var inverseMatrix = new THREE.Matrix4();

	var descSort = function ( a, b ) {

		return a.distance - b.distance;

	};

	var vA = new THREE.Vector3();
	var vB = new THREE.Vector3();
	var vC = new THREE.Vector3();

	var intersectObject = function ( object, raycaster, intersects ) {

		if ( object instanceof THREE.Sprite ) {

			matrixPosition.setFromMatrixPosition( object.matrixWorld );

			var distance = raycaster.ray.distanceToPoint( matrixPosition );

			if ( distance > object.scale.x ) {

				return intersects;

			}

			intersects.push( {

				distance: distance,
				point: object.position,
				face: null,
				object: object

			} );

		} else if ( object instanceof THREE.PointCloud ) {

			var vertices = object.geometry.vertices;

			for ( var i = 0; i < vertices.length; i ++ ) {

				var v = vertices[ i ];

				matrixPosition.copy( v ).applyMatrix4( object.matrixWorld );

				var distance = raycaster.ray.distanceToPoint( matrixPosition );

				if ( distance < 1 ) { // needs a better test; particle size?

					intersects.push( {

						distance: distance,
						index: i,
						face: null,
						object: object

					} );

				}

			}

		} else if ( object instanceof THREE.LOD ) {

			matrixPosition.setFromMatrixPosition( object.matrixWorld );
			var distance = raycaster.ray.origin.distanceTo( matrixPosition );

			intersectObject( object.getObjectForDistance( distance ), raycaster, intersects );

		} else if ( object instanceof THREE.Mesh ) {

			var geometry = object.geometry;

			// Checking boundingSphere distance to ray

			if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();

			sphere.copy( geometry.boundingSphere );
			sphere.applyMatrix4( object.matrixWorld );

			if ( raycaster.ray.isIntersectionSphere( sphere ) === false ) {

				return intersects;

			}

			// Check boundingBox before continuing

			inverseMatrix.getInverse( object.matrixWorld );
			localRay.copy( raycaster.ray ).applyMatrix4( inverseMatrix );

			if ( geometry.boundingBox !== null ) {

				if ( localRay.isIntersectionBox( geometry.boundingBox ) === false )  {

					return intersects;

				}

			}

			if ( geometry instanceof THREE.BufferGeometry ) {

				var material = object.material;

				if ( material === undefined ) return intersects;

				var attributes = geometry.attributes;

				var a, b, c;
				var precision = raycaster.precision;

				if ( attributes.index !== undefined ) {

					var indices = attributes.index.array;
					var positions = attributes.position.array;
					var offsets = geometry.offsets;

					if ( offsets.length === 0 ) {

						offsets = [ { start: 0, count: positions.length, index: 0 } ];

					}

					for ( var oi = 0, ol = offsets.length; oi < ol; ++oi ) {

						var start = offsets[ oi ].start;
						var count = offsets[ oi ].count;
						var index = offsets[ oi ].index;

						for ( var i = start, il = start + count; i < il; i += 3 ) {

							a = index + indices[ i ];
							b = index + indices[ i + 1 ];
							c = index + indices[ i + 2 ];

							vA.set(
								positions[ a * 3 ],
								positions[ a * 3 + 1 ],
								positions[ a * 3 + 2 ]
							);
							vB.set(
								positions[ b * 3 ],
								positions[ b * 3 + 1 ],
								positions[ b * 3 + 2 ]
							);
							vC.set(
								positions[ c * 3 ],
								positions[ c * 3 + 1 ],
								positions[ c * 3 + 2 ]
							);

							if ( material.side === THREE.BackSide ) {

								var intersectionPoint = localRay.intersectTriangle( vC, vB, vA, true );

							} else {

								var intersectionPoint = localRay.intersectTriangle( vA, vB, vC, material.side !== THREE.DoubleSide );

							}

							if ( intersectionPoint === null ) continue;

							intersectionPoint.applyMatrix4( object.matrixWorld );

							var distance = raycaster.ray.origin.distanceTo( intersectionPoint );

							if ( distance < precision || distance < raycaster.near || distance > raycaster.far ) continue;

							intersects.push( {

								distance: distance,
								point: intersectionPoint,
								indices: [a, b, c],
								face: null,
								faceIndex: null,
								object: object

							} );

						}

					}

				} else {

					var offsets = geometry.offsets;
					var positions = attributes.position.array;

					for ( var i = 0, il = attributes.position.array.length; i < il; i += 3 ) {

						a = i;
						b = i + 1;
						c = i + 2;

						vA.set(
							positions[ a * 3 ],
							positions[ a * 3 + 1 ],
							positions[ a * 3 + 2 ]
						);
						vB.set(
							positions[ b * 3 ],
							positions[ b * 3 + 1 ],
							positions[ b * 3 + 2 ]
						);
						vC.set(
							positions[ c * 3 ],
							positions[ c * 3 + 1 ],
							positions[ c * 3 + 2 ]
						);

						if ( material.side === THREE.BackSide ) {

							var intersectionPoint = localRay.intersectTriangle( vC, vB, vA, true );

						} else {

							var intersectionPoint = localRay.intersectTriangle( vA, vB, vC, material.side !== THREE.DoubleSide );

						}

						if ( intersectionPoint === null ) continue;

						intersectionPoint.applyMatrix4( object.matrixWorld );

						var distance = raycaster.ray.origin.distanceTo( intersectionPoint );

						if ( distance < precision || distance < raycaster.near || distance > raycaster.far ) continue;

						intersects.push( {

							distance: distance,
							point: intersectionPoint,
							indices: [a, b, c],
							face: null,
							faceIndex: null,
							object: object

						} );

					}

				}

			} else if ( geometry instanceof THREE.Geometry ) {

				var isFaceMaterial = object.material instanceof THREE.MeshFaceMaterial;
				var objectMaterials = isFaceMaterial === true ? object.material.materials : null;

				var a, b, c, d;
				var precision = raycaster.precision;

				var vertices = geometry.vertices;

				for ( var f = 0, fl = geometry.faces.length; f < fl; f ++ ) {

					var face = geometry.faces[ f ];

					var material = isFaceMaterial === true ? objectMaterials[ face.materialIndex ] : object.material;

					if ( material === undefined ) continue;

					a = vertices[ face.a ];
					b = vertices[ face.b ];
					c = vertices[ face.c ];

					if ( material.morphTargets === true ) {

						var morphTargets = geometry.morphTargets;
						var morphInfluences = object.morphTargetInfluences;

						vA.set( 0, 0, 0 );
						vB.set( 0, 0, 0 );
						vC.set( 0, 0, 0 );

						for ( var t = 0, tl = morphTargets.length; t < tl; t ++ ) {

							var influence = morphInfluences[ t ];

							if ( influence === 0 ) continue;

							var targets = morphTargets[ t ].vertices;

							vA.x += ( targets[ face.a ].x - a.x ) * influence;
							vA.y += ( targets[ face.a ].y - a.y ) * influence;
							vA.z += ( targets[ face.a ].z - a.z ) * influence;

							vB.x += ( targets[ face.b ].x - b.x ) * influence;
							vB.y += ( targets[ face.b ].y - b.y ) * influence;
							vB.z += ( targets[ face.b ].z - b.z ) * influence;

							vC.x += ( targets[ face.c ].x - c.x ) * influence;
							vC.y += ( targets[ face.c ].y - c.y ) * influence;
							vC.z += ( targets[ face.c ].z - c.z ) * influence;

						}

						vA.add( a );
						vB.add( b );
						vC.add( c );

						a = vA;
						b = vB;
						c = vC;

					}

					if ( material.side === THREE.BackSide ) {

						var intersectionPoint = localRay.intersectTriangle( c, b, a, true );

					} else {

						var intersectionPoint = localRay.intersectTriangle( a, b, c, material.side !== THREE.DoubleSide );

					}

					if ( intersectionPoint === null ) continue;

					intersectionPoint.applyMatrix4( object.matrixWorld );

					var distance = raycaster.ray.origin.distanceTo( intersectionPoint );

					if ( distance < precision || distance < raycaster.near || distance > raycaster.far ) continue;

					intersects.push( {

						distance: distance,
						point: intersectionPoint,
						face: face,
						faceIndex: f,
						object: object

					} );

				}

			}

		} else if ( object instanceof THREE.Line ) {

			var precision = raycaster.linePrecision;
			var precisionSq = precision * precision;

			var geometry = object.geometry;

			if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();

			// Checking boundingSphere distance to ray

			sphere.copy( geometry.boundingSphere );
			sphere.applyMatrix4( object.matrixWorld );

			if ( raycaster.ray.isIntersectionSphere( sphere ) === false ) {

				return intersects;

			}

			inverseMatrix.getInverse( object.matrixWorld );
			localRay.copy( raycaster.ray ).applyMatrix4( inverseMatrix );

			/* if ( geometry instanceof THREE.BufferGeometry ) {

			} else */ if ( geometry instanceof THREE.Geometry ) {

				var vertices = geometry.vertices;
				var nbVertices = vertices.length;
				var interSegment = new THREE.Vector3();
				var interRay = new THREE.Vector3();
				var step = object.type === THREE.LineStrip ? 1 : 2;

				for ( var i = 0; i < nbVertices - 1; i = i + step ) {

					var distSq = localRay.distanceSqToSegment( vertices[ i ], vertices[ i + 1 ], interRay, interSegment );

					if ( distSq > precisionSq ) continue;

					var distance = localRay.origin.distanceTo( interRay );

					if ( distance < raycaster.near || distance > raycaster.far ) continue;

					intersects.push( {

						distance: distance,
						// What do we want? intersection point on the ray or on the segment??
						// point: raycaster.ray.at( distance ),
						point: interSegment.clone().applyMatrix4( object.matrixWorld ),
						face: null,
						faceIndex: null,
						object: object

					} );

				}

			}

		}

	};

	var intersectDescendants = function ( object, raycaster, intersects ) {

		var descendants = object.getDescendants();

		for ( var i = 0, l = descendants.length; i < l; i ++ ) {

			intersectObject( descendants[ i ], raycaster, intersects );

		}
	};

	//

	THREE.Raycaster.prototype.precision = 0.0001;
	THREE.Raycaster.prototype.linePrecision = 1;

	THREE.Raycaster.prototype.set = function ( origin, direction ) {

		this.ray.set( origin, direction );
		// direction is assumed to be normalized (for accurate distance calculations)

	};

	THREE.Raycaster.prototype.intersectObject = function ( object, recursive ) {

		var intersects = [];

		if ( recursive === true ) {

			intersectDescendants( object, this, intersects );

		}

		intersectObject( object, this, intersects );

		intersects.sort( descSort );

		return intersects;

	};

	THREE.Raycaster.prototype.intersectObjects = function ( objects, recursive ) {

		var intersects = [];

		for ( var i = 0, l = objects.length; i < l; i ++ ) {

			intersectObject( objects[ i ], this, intersects );

			if ( recursive === true ) {

				intersectDescendants( objects[ i ], this, intersects );

			}

		}

		intersects.sort( descSort );

		return intersects;

	};

}( THREE ) );
<commit_msg>Implemented @m-schuetz's bugfix for indexed BufferGeometry in Raycaster. See #4768.<commit_after>/**
 * @author mrdoob / http://mrdoob.com/
 * @author bhouston / http://exocortex.com/
 * @author stephomi / http://stephaneginier.com/
 */

( function ( THREE ) {

	THREE.Raycaster = function ( origin, direction, near, far ) {

		this.ray = new THREE.Ray( origin, direction );
		// direction is assumed to be normalized (for accurate distance calculations)

		this.near = near || 0;
		this.far = far || Infinity;

	};

	var sphere = new THREE.Sphere();
	var localRay = new THREE.Ray();
	var facePlane = new THREE.Plane();
	var intersectPoint = new THREE.Vector3();
	var matrixPosition = new THREE.Vector3();

	var inverseMatrix = new THREE.Matrix4();

	var descSort = function ( a, b ) {

		return a.distance - b.distance;

	};

	var vA = new THREE.Vector3();
	var vB = new THREE.Vector3();
	var vC = new THREE.Vector3();

	var intersectObject = function ( object, raycaster, intersects ) {

		if ( object instanceof THREE.Sprite ) {

			matrixPosition.setFromMatrixPosition( object.matrixWorld );

			var distance = raycaster.ray.distanceToPoint( matrixPosition );

			if ( distance > object.scale.x ) {

				return intersects;

			}

			intersects.push( {

				distance: distance,
				point: object.position,
				face: null,
				object: object

			} );

		} else if ( object instanceof THREE.PointCloud ) {

			var vertices = object.geometry.vertices;

			for ( var i = 0; i < vertices.length; i ++ ) {

				var v = vertices[ i ];

				matrixPosition.copy( v ).applyMatrix4( object.matrixWorld );

				var distance = raycaster.ray.distanceToPoint( matrixPosition );

				if ( distance < 1 ) { // needs a better test; particle size?

					intersects.push( {

						distance: distance,
						index: i,
						face: null,
						object: object

					} );

				}

			}

		} else if ( object instanceof THREE.LOD ) {

			matrixPosition.setFromMatrixPosition( object.matrixWorld );
			var distance = raycaster.ray.origin.distanceTo( matrixPosition );

			intersectObject( object.getObjectForDistance( distance ), raycaster, intersects );

		} else if ( object instanceof THREE.Mesh ) {

			var geometry = object.geometry;

			// Checking boundingSphere distance to ray

			if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();

			sphere.copy( geometry.boundingSphere );
			sphere.applyMatrix4( object.matrixWorld );

			if ( raycaster.ray.isIntersectionSphere( sphere ) === false ) {

				return intersects;

			}

			// Check boundingBox before continuing

			inverseMatrix.getInverse( object.matrixWorld );
			localRay.copy( raycaster.ray ).applyMatrix4( inverseMatrix );

			if ( geometry.boundingBox !== null ) {

				if ( localRay.isIntersectionBox( geometry.boundingBox ) === false )  {

					return intersects;

				}

			}

			if ( geometry instanceof THREE.BufferGeometry ) {

				var material = object.material;

				if ( material === undefined ) return intersects;

				var attributes = geometry.attributes;

				var a, b, c;
				var precision = raycaster.precision;

				if ( attributes.index !== undefined ) {

					var indices = attributes.index.array;
					var positions = attributes.position.array;
					var offsets = geometry.offsets;

					if ( offsets.length === 0 ) {

						offsets = [ { start: 0, count: indices.length, index: 0 } ];

					}

					for ( var oi = 0, ol = offsets.length; oi < ol; ++oi ) {

						var start = offsets[ oi ].start;
						var count = offsets[ oi ].count;
						var index = offsets[ oi ].index;

						for ( var i = start, il = start + count; i < il; i += 3 ) {

							a = index + indices[ i ];
							b = index + indices[ i + 1 ];
							c = index + indices[ i + 2 ];

							vA.set(
								positions[ a * 3 ],
								positions[ a * 3 + 1 ],
								positions[ a * 3 + 2 ]
							);
							vB.set(
								positions[ b * 3 ],
								positions[ b * 3 + 1 ],
								positions[ b * 3 + 2 ]
							);
							vC.set(
								positions[ c * 3 ],
								positions[ c * 3 + 1 ],
								positions[ c * 3 + 2 ]
							);

							if ( material.side === THREE.BackSide ) {

								var intersectionPoint = localRay.intersectTriangle( vC, vB, vA, true );

							} else {

								var intersectionPoint = localRay.intersectTriangle( vA, vB, vC, material.side !== THREE.DoubleSide );

							}

							if ( intersectionPoint === null ) continue;

							intersectionPoint.applyMatrix4( object.matrixWorld );

							var distance = raycaster.ray.origin.distanceTo( intersectionPoint );

							if ( distance < precision || distance < raycaster.near || distance > raycaster.far ) continue;

							intersects.push( {

								distance: distance,
								point: intersectionPoint,
								indices: [a, b, c],
								face: null,
								faceIndex: null,
								object: object

							} );

						}

					}

				} else {

					var offsets = geometry.offsets;
					var positions = attributes.position.array;

					for ( var i = 0, il = attributes.position.array.length; i < il; i += 3 ) {

						a = i;
						b = i + 1;
						c = i + 2;

						vA.set(
							positions[ a * 3 ],
							positions[ a * 3 + 1 ],
							positions[ a * 3 + 2 ]
						);
						vB.set(
							positions[ b * 3 ],
							positions[ b * 3 + 1 ],
							positions[ b * 3 + 2 ]
						);
						vC.set(
							positions[ c * 3 ],
							positions[ c * 3 + 1 ],
							positions[ c * 3 + 2 ]
						);

						if ( material.side === THREE.BackSide ) {

							var intersectionPoint = localRay.intersectTriangle( vC, vB, vA, true );

						} else {

							var intersectionPoint = localRay.intersectTriangle( vA, vB, vC, material.side !== THREE.DoubleSide );

						}

						if ( intersectionPoint === null ) continue;

						intersectionPoint.applyMatrix4( object.matrixWorld );

						var distance = raycaster.ray.origin.distanceTo( intersectionPoint );

						if ( distance < precision || distance < raycaster.near || distance > raycaster.far ) continue;

						intersects.push( {

							distance: distance,
							point: intersectionPoint,
							indices: [a, b, c],
							face: null,
							faceIndex: null,
							object: object

						} );

					}

				}

			} else if ( geometry instanceof THREE.Geometry ) {

				var isFaceMaterial = object.material instanceof THREE.MeshFaceMaterial;
				var objectMaterials = isFaceMaterial === true ? object.material.materials : null;

				var a, b, c, d;
				var precision = raycaster.precision;

				var vertices = geometry.vertices;

				for ( var f = 0, fl = geometry.faces.length; f < fl; f ++ ) {

					var face = geometry.faces[ f ];

					var material = isFaceMaterial === true ? objectMaterials[ face.materialIndex ] : object.material;

					if ( material === undefined ) continue;

					a = vertices[ face.a ];
					b = vertices[ face.b ];
					c = vertices[ face.c ];

					if ( material.morphTargets === true ) {

						var morphTargets = geometry.morphTargets;
						var morphInfluences = object.morphTargetInfluences;

						vA.set( 0, 0, 0 );
						vB.set( 0, 0, 0 );
						vC.set( 0, 0, 0 );

						for ( var t = 0, tl = morphTargets.length; t < tl; t ++ ) {

							var influence = morphInfluences[ t ];

							if ( influence === 0 ) continue;

							var targets = morphTargets[ t ].vertices;

							vA.x += ( targets[ face.a ].x - a.x ) * influence;
							vA.y += ( targets[ face.a ].y - a.y ) * influence;
							vA.z += ( targets[ face.a ].z - a.z ) * influence;

							vB.x += ( targets[ face.b ].x - b.x ) * influence;
							vB.y += ( targets[ face.b ].y - b.y ) * influence;
							vB.z += ( targets[ face.b ].z - b.z ) * influence;

							vC.x += ( targets[ face.c ].x - c.x ) * influence;
							vC.y += ( targets[ face.c ].y - c.y ) * influence;
							vC.z += ( targets[ face.c ].z - c.z ) * influence;

						}

						vA.add( a );
						vB.add( b );
						vC.add( c );

						a = vA;
						b = vB;
						c = vC;

					}

					if ( material.side === THREE.BackSide ) {

						var intersectionPoint = localRay.intersectTriangle( c, b, a, true );

					} else {

						var intersectionPoint = localRay.intersectTriangle( a, b, c, material.side !== THREE.DoubleSide );

					}

					if ( intersectionPoint === null ) continue;

					intersectionPoint.applyMatrix4( object.matrixWorld );

					var distance = raycaster.ray.origin.distanceTo( intersectionPoint );

					if ( distance < precision || distance < raycaster.near || distance > raycaster.far ) continue;

					intersects.push( {

						distance: distance,
						point: intersectionPoint,
						face: face,
						faceIndex: f,
						object: object

					} );

				}

			}

		} else if ( object instanceof THREE.Line ) {

			var precision = raycaster.linePrecision;
			var precisionSq = precision * precision;

			var geometry = object.geometry;

			if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();

			// Checking boundingSphere distance to ray

			sphere.copy( geometry.boundingSphere );
			sphere.applyMatrix4( object.matrixWorld );

			if ( raycaster.ray.isIntersectionSphere( sphere ) === false ) {

				return intersects;

			}

			inverseMatrix.getInverse( object.matrixWorld );
			localRay.copy( raycaster.ray ).applyMatrix4( inverseMatrix );

			/* if ( geometry instanceof THREE.BufferGeometry ) {

			} else */ if ( geometry instanceof THREE.Geometry ) {

				var vertices = geometry.vertices;
				var nbVertices = vertices.length;
				var interSegment = new THREE.Vector3();
				var interRay = new THREE.Vector3();
				var step = object.type === THREE.LineStrip ? 1 : 2;

				for ( var i = 0; i < nbVertices - 1; i = i + step ) {

					var distSq = localRay.distanceSqToSegment( vertices[ i ], vertices[ i + 1 ], interRay, interSegment );

					if ( distSq > precisionSq ) continue;

					var distance = localRay.origin.distanceTo( interRay );

					if ( distance < raycaster.near || distance > raycaster.far ) continue;

					intersects.push( {

						distance: distance,
						// What do we want? intersection point on the ray or on the segment??
						// point: raycaster.ray.at( distance ),
						point: interSegment.clone().applyMatrix4( object.matrixWorld ),
						face: null,
						faceIndex: null,
						object: object

					} );

				}

			}

		}

	};

	var intersectDescendants = function ( object, raycaster, intersects ) {

		var descendants = object.getDescendants();

		for ( var i = 0, l = descendants.length; i < l; i ++ ) {

			intersectObject( descendants[ i ], raycaster, intersects );

		}
	};

	//

	THREE.Raycaster.prototype.precision = 0.0001;
	THREE.Raycaster.prototype.linePrecision = 1;

	THREE.Raycaster.prototype.set = function ( origin, direction ) {

		this.ray.set( origin, direction );
		// direction is assumed to be normalized (for accurate distance calculations)

	};

	THREE.Raycaster.prototype.intersectObject = function ( object, recursive ) {

		var intersects = [];

		if ( recursive === true ) {

			intersectDescendants( object, this, intersects );

		}

		intersectObject( object, this, intersects );

		intersects.sort( descSort );

		return intersects;

	};

	THREE.Raycaster.prototype.intersectObjects = function ( objects, recursive ) {

		var intersects = [];

		for ( var i = 0, l = objects.length; i < l; i ++ ) {

			intersectObject( objects[ i ], this, intersects );

			if ( recursive === true ) {

				intersectDescendants( objects[ i ], this, intersects );

			}

		}

		intersects.sort( descSort );

		return intersects;

	};

}( THREE ) );
<|endoftext|>


<commit_before>import {isFunction, isArray, isObject, isString, pop, assign} from 'd3-let';
import {map} from 'd3-collection';
import {dispatch} from 'd3-dispatch';

import base from './transition';
import createDirective from './directive';
import warn from '../utils/warn';
import asSelect from '../utils/select';
import maybeJson from '../utils/maybeJson';
import sel from '../utils/sel';
import dataAttributes from '../utils/data';
import viewEvents from './events';

// prototype for both views and components
const protoComponent = {
    //
    // hooks
    render () {},
    childrenMounted () {},
    mounted () {},
    destroy () {},
    //
    // Mount the component into an element
    // If this component is already mounted, or it is mounting, it does nothing
    mount (el, data, onMounted) {
        if (mounted(this)) return;
        viewEvents.call('component-mount', undefined, this, el, data);
        var sel = this.select(el),
            directives = sel.directives(),
            dattrs = directives ? directives.attrs : attributes(el),
            parentModel = this.parent.model,
            datum = sel.datum();

        let props = this.props,
            model = this.model,
            modelData = assign(dataAttributes(dattrs), datum, data),
            key, value;

        data = assign({}, datum, data);

        // override model keys from data object and element attributes
        for (key in model) {
            value = pop(modelData, key);
            if (value !== undefined) {
                if (isString(value)) {
                    if (parentModel.$isReactive(value)) {
                        if (value !== key) model[key] = reactiveParentProperty(value);
                        else delete model[key];
                    } else model[key] = maybeJson(value);
                } else model[key] = value;
            }
        }

        // Create model
        this.model = model = parentModel.$child(model);
        model.$$view = this;
        model.$$name = this.name;
        if (isArray(props)) props = props.reduce((o, key) => {
            o[key] = undefined;
            return o;
        }, {});

        if (isObject(props)) {
            Object.keys(props).forEach(key => {
                value = maybeJson(modelData[key] === undefined ? (data[key] === undefined ? dattrs[key] : data[key]) : modelData[key]);
                if (value !== undefined) {
                    // data point to a model attribute
                    if (isString(value) && model[value]) value = model[value];
                    data[key] = value;
                } else if (props[key] !== undefined) {
                    data[key] = props[key];
                }
            });
        }
        //
        // create the new element from the render function
        var newEl = this.render(data, dattrs, el);
        if (!newEl.then) newEl = Promise.resolve(newEl);
        return newEl.then(element => compile(this, el, element, onMounted));
    }
};

// factory of View and Component constructors
export function createComponent (name, o, coreDirectives) {
    if (isFunction(o)) o = {render: o};

    var obj = assign({}, o),
        classComponents = extendComponents(map(), pop(obj, 'components')),
        classDirectives = extendDirectives(map(), pop(obj, 'directives')),
        model = pop(obj, 'model'),
        props = pop(obj, 'props');

    function Component (options) {
        var parent = pop(options, 'parent'),
            components = map(parent ? parent.components : null),
            directives = map(parent ? parent.directives : coreDirectives),
            events = dispatch('message', 'mounted'),
            cache = {};

        classComponents.each((comp, key) => {
            components.set(key, comp);
        });
        classDirectives.each((comp, key) => {
            directives.set(key, comp);
        });
        extendComponents(components, pop(options, 'components'));
        extendDirectives(directives, pop(options, 'directives'));

        Object.defineProperties(this, {
            name : {
                get () {
                    return name;
                }
            },
            components: {
                get: function () {
                    return components;
                }
            },
            directives: {
                get: function () {
                    return directives;
                }
            },
            parent: {
                get: function () {
                    return parent;
                }
            },
            root: {
                get: function () {
                    return parent ? parent.root : this;
                }
            },
            cache: {
                get: function () {
                    return parent ? parent.cache : cache;
                }
            },
            props: {
                get: function () {
                    return props;
                }
            },
            uid: {
                get: function () {
                    return this.model.uid;
                }
            },
            events: {
                get: function () {
                    return events;
                }
            }
        });
        this.model = assign({}, isFunction(model) ? model() : model, pop(options, 'model'));
        viewEvents.call('component-created', undefined, this);
    }

    Component.prototype = assign({}, base, protoComponent, obj);

    function component (options) {
        return new Component(options);
    }

    component.prototype = Component.prototype;

    return component;
}

// Used by both Component and view

export function extendComponents (container, components) {
    map(components).each((obj, key) => {
        container.set(key, createComponent(key, obj, protoComponent));
    });
    return container;
}

export function extendDirectives (container, directives) {
    map(directives).each((obj, key) => {
        container.set(key, createDirective(obj));
    });
    return container;
}

// Finalise the binding between the view and the model
// inject the model into the view element
// call the mounted hook and can return a Promise
export function asView(vm, element, onMounted) {
    Object.defineProperty(sel(vm), 'el', {
        get: function () {
            return element;
        }
    });
    // Apply model to element and mount
    return vm.select(element).view(vm).mount(null, onMounted).then(() => vmMounted(vm, onMounted));
}

export function mounted (vm, onMounted) {
    if (vm.isMounted === undefined) {
        vm.isMounted = false;
        return false;
    }
    else if (vm.isMounted) {
        vm.logWarn(`component already mounted`);
    }
    else {
        vm.isMounted = true;
        // invoke mounted component hook
        vm.mounted();
        // invoke onMounted callback if available
        if (onMounted) onMounted(vm);
        // last invoke the view mounted events
        vm.events.call('mounted', undefined, vm, onMounted);
        // remove mounted events
        vm.events.on('mounted', null);
        // fire global event
        viewEvents.call('component-mounted', undefined, vm);
    }
    return true;
}

// Internals

//
//  Component/View mounted
//  =========================
//
//  This function is called when a component/view has all its children added
function vmMounted(vm, onMounted) {
    var parent = vm.parent;
    vm.childrenMounted();
    if (parent && !parent.isMounted) {
        parent.events.on(`mounted.${vm.uid}`, () => {
            mounted(vm, onMounted);
        });
    }
    else {
        mounted(vm, onMounted);
    }
}

// Compile a component model
// This function is called once a component has rendered the component element
function compile (cm, el, element, onMounted) {
    if (!element) return warn('render function must return a single HTML node. It returned nothing!');
    element = asSelect(element);
    if (element.size() !== 1) warn('render function must return a single HTML node');
    element = element.node();
    //
    // Insert before the component element
    el.parentNode.insertBefore(element, el);
    // remove the component element
    cm.select(el).remove();
    //
    return asView(cm, element, onMounted);
}

function attributes (element) {
    var attrs = {};
    let attr;
    for (let i = 0; i < element.attributes.length; ++i) {
        attr = element.attributes[i];
        attrs[attr.name] = attr.value;
    }
    return attrs;
}

function reactiveParentProperty (value) {
    return {
        reactOn: [value],
        get () {
            return this[value];
        }
    };
}
<commit_msg>inject 95614view consistently<commit_after>import {isFunction, isArray, isObject, isString, pop, assign} from 'd3-let';
import {map} from 'd3-collection';
import {dispatch} from 'd3-dispatch';

import base from './transition';
import createDirective from './directive';
import warn from '../utils/warn';
import asSelect from '../utils/select';
import maybeJson from '../utils/maybeJson';
import sel from '../utils/sel';
import dataAttributes from '../utils/data';
import viewEvents from './events';

// prototype for both views and components
const protoComponent = {
    //
    // hooks
    render () {},
    childrenMounted () {},
    mounted () {},
    destroy () {},
    //
    // Mount the component into an element
    // If this component is already mounted, or it is mounting, it does nothing
    mount (el, data, onMounted) {
        if (mounted(this)) return;
        viewEvents.call('component-mount', undefined, this, el, data);
        var sel = this.select(el),
            directives = sel.directives(),
            dattrs = directives ? directives.attrs : attributes(el),
            parentModel = this.parent.model,
            datum = sel.datum();

        let props = this.props,
            model = this.model,
            modelData = assign(dataAttributes(dattrs), datum, data),
            key, value;

        data = assign({}, datum, data);

        // override model keys from data object and element attributes
        for (key in model) {
            value = pop(modelData, key);
            if (value !== undefined) {
                if (isString(value)) {
                    if (parentModel.$isReactive(value)) {
                        if (value !== key) model[key] = reactiveParentProperty(value);
                        else delete model[key];
                    } else model[key] = maybeJson(value);
                } else model[key] = value;
            }
        }

        // Create model
        this.model = model = parentModel.$child(model);
        if (isArray(props)) props = props.reduce((o, key) => {
            o[key] = undefined;
            return o;
        }, {});

        if (isObject(props)) {
            Object.keys(props).forEach(key => {
                value = maybeJson(modelData[key] === undefined ? (data[key] === undefined ? dattrs[key] : data[key]) : modelData[key]);
                if (value !== undefined) {
                    // data point to a model attribute
                    if (isString(value) && model[value]) value = model[value];
                    data[key] = value;
                } else if (props[key] !== undefined) {
                    data[key] = props[key];
                }
            });
        }
        //
        // create the new element from the render function
        var newEl = this.render(data, dattrs, el);
        if (!newEl.then) newEl = Promise.resolve(newEl);
        return newEl.then(element => compile(this, el, element, onMounted));
    }
};

// factory of View and Component constructors
export function createComponent (name, o, coreDirectives) {
    if (isFunction(o)) o = {render: o};

    var obj = assign({}, o),
        classComponents = extendComponents(map(), pop(obj, 'components')),
        classDirectives = extendDirectives(map(), pop(obj, 'directives')),
        model = pop(obj, 'model'),
        props = pop(obj, 'props');

    function Component (options) {
        var parent = pop(options, 'parent'),
            components = map(parent ? parent.components : null),
            directives = map(parent ? parent.directives : coreDirectives),
            events = dispatch('message', 'mounted'),
            cache = {};

        classComponents.each((comp, key) => {
            components.set(key, comp);
        });
        classDirectives.each((comp, key) => {
            directives.set(key, comp);
        });
        extendComponents(components, pop(options, 'components'));
        extendDirectives(directives, pop(options, 'directives'));

        Object.defineProperties(this, {
            name : {
                get () {
                    return name;
                }
            },
            components: {
                get: function () {
                    return components;
                }
            },
            directives: {
                get: function () {
                    return directives;
                }
            },
            parent: {
                get: function () {
                    return parent;
                }
            },
            root: {
                get: function () {
                    return parent ? parent.root : this;
                }
            },
            cache: {
                get: function () {
                    return parent ? parent.cache : cache;
                }
            },
            props: {
                get: function () {
                    return props;
                }
            },
            uid: {
                get: function () {
                    return this.model.uid;
                }
            },
            events: {
                get: function () {
                    return events;
                }
            }
        });
        this.model = assign({}, isFunction(model) ? model() : model, pop(options, 'model'));
        viewEvents.call('component-created', undefined, this);
    }

    Component.prototype = assign({}, base, protoComponent, obj);

    function component (options) {
        return new Component(options);
    }

    component.prototype = Component.prototype;

    return component;
}

// Used by both Component and view

export function extendComponents (container, components) {
    map(components).each((obj, key) => {
        container.set(key, createComponent(key, obj, protoComponent));
    });
    return container;
}

export function extendDirectives (container, directives) {
    map(directives).each((obj, key) => {
        container.set(key, createDirective(obj));
    });
    return container;
}

//
//  Finalise the binding between the view and the model
//  inject the model into the view element
//  call the mounted hook and can return a Promise
export function asView(vm, element, onMounted) {
    var model = vm.model;
    model.$$view = vm;
    model.$$name = vm.name;

    Object.defineProperty(sel(vm), 'el', {
        get: function () {
            return element;
        }
    });
    // Apply model to element and mount
    return vm.select(element).view(vm).mount(null, onMounted).then(() => vmMounted(vm, onMounted));
}

export function mounted (vm, onMounted) {
    if (vm.isMounted === undefined) {
        vm.isMounted = false;
        return false;
    }
    else if (vm.isMounted) {
        vm.logWarn(`component already mounted`);
    }
    else {
        vm.isMounted = true;
        // invoke mounted component hook
        vm.mounted();
        // invoke onMounted callback if available
        if (onMounted) onMounted(vm);
        // last invoke the view mounted events
        vm.events.call('mounted', undefined, vm, onMounted);
        // remove mounted events
        vm.events.on('mounted', null);
        // fire global event
        viewEvents.call('component-mounted', undefined, vm);
    }
    return true;
}

// Internals

//
//  Component/View mounted
//  =========================
//
//  This function is called when a component/view has all its children added
function vmMounted(vm, onMounted) {
    var parent = vm.parent;
    vm.childrenMounted();
    if (parent && !parent.isMounted) {
        parent.events.on(`mounted.${vm.uid}`, () => {
            mounted(vm, onMounted);
        });
    }
    else {
        mounted(vm, onMounted);
    }
}

// Compile a component model
// This function is called once a component has rendered the component element
function compile (cm, el, element, onMounted) {
    if (!element) return warn('render function must return a single HTML node. It returned nothing!');
    element = asSelect(element);
    if (element.size() !== 1) warn('render function must return a single HTML node');
    element = element.node();
    //
    // Insert before the component element
    el.parentNode.insertBefore(element, el);
    // remove the component element
    cm.select(el).remove();
    //
    return asView(cm, element, onMounted);
}

function attributes (element) {
    var attrs = {};
    let attr;
    for (let i = 0; i < element.attributes.length; ++i) {
        attr = element.attributes[i];
        attrs[attr.name] = attr.value;
    }
    return attrs;
}

function reactiveParentProperty (value) {
    return {
        reactOn: [value],
        get () {
            return this[value];
        }
    };
}
<|endoftext|>


<commit_before>// This file is generated, do not edit.
module.exports = {
  "availableEvents": [
    "SwitchScenes",
    "ScenesChanged",
    "SceneCollectionChanged",
    "SceneCollectionListChanged",
    "SwitchTransition",
    "TransitionListChanged",
    "TransitionDurationChanged",
    "TransitionBegin",
    "ProfileChanged",
    "ProfileListChanged",
    "StreamStarting",
    "StreamStarted",
    "StreamStopping",
    "StreamStopped",
    "StreamStatus",
    "RecordingStarting",
    "RecordingStarted",
    "RecordingStopping",
    "RecordingStopped",
    "ReplayStarting",
    "ReplayStarted",
    "ReplayStopping",
    "ReplayStopped",
    "Exiting",
    "Heartbeat",
    "SourceOrderChanged",
    "SceneItemAdded",
    "SceneItemRemoved",
    "SceneItemVisibilityChanged",
    "PreviewSceneChanged",
    "StudioModeSwitched"
  ],
  "availableMethods": [
    "GetVersion",
    "GetAuthRequired",
    "Authenticate",
    "SetHeartbeat",
    "SetCurrentProfile",
    "GetCurrentProfile",
    "ListProfiles",
    "StartStopRecording",
    "StartRecording",
    "StopRecording",
    "SetRecordingFolder",
    "GetRecordingFolder",
    "StartStopReplayBuffer",
    "StartReplayBuffer",
    "StopReplayBuffer",
    "SaveReplayBuffer",
    "SetCurrentSceneCollection",
    "GetCurrentSceneCollection",
    "ListSceneCollections",
    "GetSceneItemProperties",
    "SetSceneItemProperties",
    "ResetSceneItem",
    "SetSceneItemRender",
    "SetSceneItemPosition",
    "SetSceneItemTransform",
    "SetSceneItemCrop",
    "SetCurrentScene",
    "GetCurrentScene",
    "GetSceneList",
    "GetSourcesList",
    "GetSourcesTypesList",
    "GetVolume",
    "SetVolume",
    "GetMute",
    "SetMute",
    "ToggleMute",
    "SetSyncOffset",
    "GetSyncOffset",
    "GetSourceSettings",
    "SetSourceSettings",
    "GetTextGDIPlusProperties",
    "SetTextGDIPlusProperties",
    "GetBrowserSourceProperties",
    "SetBrowserSourceProperties",
    "GetSpecialSources",
    "GetStreamingStatus",
    "StartStopStreaming",
    "StartStreaming",
    "StopStreaming",
    "SetStreamSettings",
    "GetStreamSettings",
    "SaveStreamSettings",
    "GetStudioModeStatus",
    "GetPreviewScene",
    "SetPreviewScene",
    "TransitionToProgram",
    "EnableStudioMode",
    "DisableStudioMode",
    "ToggleStudioMode",
    "GetTransitionList",
    "GetCurrentTransition",
    "SetCurrentTransition",
    "SetTransitionDuration",
    "GetTransitionDuration"
  ]
}<commit_msg>api(ci): Update available obs-websocket methods<commit_after>// This file is generated, do not edit.
module.exports = {
  "availableEvents": [
    "SwitchScenes",
    "ScenesChanged",
    "SceneCollectionChanged",
    "SceneCollectionListChanged",
    "SwitchTransition",
    "TransitionListChanged",
    "TransitionDurationChanged",
    "TransitionBegin",
    "ProfileChanged",
    "ProfileListChanged",
    "StreamStarting",
    "StreamStarted",
    "StreamStopping",
    "StreamStopped",
    "StreamStatus",
    "RecordingStarting",
    "RecordingStarted",
    "RecordingStopping",
    "RecordingStopped",
    "ReplayStarting",
    "ReplayStarted",
    "ReplayStopping",
    "ReplayStopped",
    "Exiting",
    "Heartbeat",
    "SourceOrderChanged",
    "SceneItemAdded",
    "SceneItemRemoved",
    "SceneItemVisibilityChanged",
    "PreviewSceneChanged",
    "StudioModeSwitched"
  ],
  "availableMethods": [
    "GetVersion",
    "GetAuthRequired",
    "Authenticate",
    "SetHeartbeat",
    "SetFilenameFormatting",
    "GetFilenameFormatting",
    "SetCurrentProfile",
    "GetCurrentProfile",
    "ListProfiles",
    "StartStopRecording",
    "StartRecording",
    "StopRecording",
    "SetRecordingFolder",
    "GetRecordingFolder",
    "StartStopReplayBuffer",
    "StartReplayBuffer",
    "StopReplayBuffer",
    "SaveReplayBuffer",
    "SetCurrentSceneCollection",
    "GetCurrentSceneCollection",
    "ListSceneCollections",
    "GetSceneItemProperties",
    "SetSceneItemProperties",
    "ResetSceneItem",
    "SetSceneItemRender",
    "SetSceneItemPosition",
    "SetSceneItemTransform",
    "SetSceneItemCrop",
    "SetCurrentScene",
    "GetCurrentScene",
    "GetSceneList",
    "GetSourcesList",
    "GetSourcesTypesList",
    "GetVolume",
    "SetVolume",
    "GetMute",
    "SetMute",
    "ToggleMute",
    "SetSyncOffset",
    "GetSyncOffset",
    "GetSourceSettings",
    "SetSourceSettings",
    "GetTextGDIPlusProperties",
    "SetTextGDIPlusProperties",
    "GetBrowserSourceProperties",
    "SetBrowserSourceProperties",
    "GetSpecialSources",
    "GetStreamingStatus",
    "StartStopStreaming",
    "StartStreaming",
    "StopStreaming",
    "SetStreamSettings",
    "GetStreamSettings",
    "SaveStreamSettings",
    "GetStudioModeStatus",
    "GetPreviewScene",
    "SetPreviewScene",
    "TransitionToProgram",
    "EnableStudioMode",
    "DisableStudioMode",
    "ToggleStudioMode",
    "GetTransitionList",
    "GetCurrentTransition",
    "SetCurrentTransition",
    "SetTransitionDuration",
    "GetTransitionDuration"
  ]
}<|endoftext|>