michael@0: /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: "use strict"; michael@0: michael@0: const {Cu} = require("chrome"); michael@0: michael@0: let {TiltVisualizer} = require("devtools/tilt/tilt-visualizer"); michael@0: let TiltGL = require("devtools/tilt/tilt-gl"); michael@0: let TiltUtils = require("devtools/tilt/tilt-utils"); michael@0: let EventEmitter = require("devtools/toolkit/event-emitter"); michael@0: let Telemetry = require("devtools/shared/telemetry"); michael@0: michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: michael@0: // Tilt notifications dispatched through the nsIObserverService. michael@0: const TILT_NOTIFICATIONS = { michael@0: // Called early in the startup of a new tilt instance michael@0: STARTUP: "tilt-startup", michael@0: michael@0: // Fires when Tilt starts the initialization. michael@0: INITIALIZING: "tilt-initializing", michael@0: michael@0: // Fires immediately after initialization is complete. michael@0: // (when the canvas overlay is visible and the 3D mesh is completely created) michael@0: INITIALIZED: "tilt-initialized", michael@0: michael@0: // Fires immediately before the destruction is started. michael@0: DESTROYING: "tilt-destroying", michael@0: michael@0: // Fires immediately before the destruction is finished. michael@0: // (just before the canvas overlay is removed from its parent node) michael@0: BEFORE_DESTROYED: "tilt-before-destroyed", michael@0: michael@0: // Fires when Tilt is completely destroyed. michael@0: DESTROYED: "tilt-destroyed", michael@0: michael@0: // Fires when Tilt is shown (after a tab-switch). michael@0: SHOWN: "tilt-shown", michael@0: michael@0: // Fires when Tilt is hidden (after a tab-switch). michael@0: HIDDEN: "tilt-hidden", michael@0: michael@0: // Fires once Tilt highlights an element in the page. michael@0: HIGHLIGHTING: "tilt-highlighting", michael@0: michael@0: // Fires once Tilt stops highlighting any element. michael@0: UNHIGHLIGHTING: "tilt-unhighlighting", michael@0: michael@0: // Fires when a node is removed from the 3D mesh. michael@0: NODE_REMOVED: "tilt-node-removed" michael@0: }; michael@0: michael@0: let TiltManager = { michael@0: _instances: new WeakMap(), michael@0: getTiltForBrowser: function(aChromeWindow) michael@0: { michael@0: if (this._instances.has(aChromeWindow)) { michael@0: return this._instances.get(aChromeWindow); michael@0: } else { michael@0: let tilt = new Tilt(aChromeWindow); michael@0: this._instances.set(aChromeWindow, tilt); michael@0: return tilt; michael@0: } michael@0: }, michael@0: } michael@0: michael@0: exports.TiltManager = TiltManager; michael@0: michael@0: /** michael@0: * Object managing instances of the visualizer. michael@0: * michael@0: * @param {Window} aWindow michael@0: * the chrome window used by each visualizer instance michael@0: */ michael@0: function Tilt(aWindow) michael@0: { michael@0: /** michael@0: * Save a reference to the top-level window. michael@0: */ michael@0: this.chromeWindow = aWindow; michael@0: michael@0: /** michael@0: * All the instances of TiltVisualizer. michael@0: */ michael@0: this.visualizers = {}; michael@0: michael@0: /** michael@0: * Shortcut for accessing notifications strings. michael@0: */ michael@0: this.NOTIFICATIONS = TILT_NOTIFICATIONS; michael@0: michael@0: EventEmitter.decorate(this); michael@0: michael@0: this.setup(); michael@0: michael@0: this._telemetry = new Telemetry(); michael@0: } michael@0: michael@0: Tilt.prototype = { michael@0: michael@0: /** michael@0: * Initializes a visualizer for the current tab or closes it if already open. michael@0: */ michael@0: toggle: function T_toggle() michael@0: { michael@0: let contentWindow = this.chromeWindow.gBrowser.selectedBrowser.contentWindow; michael@0: let id = this.currentWindowId; michael@0: let self = this; michael@0: michael@0: contentWindow.addEventListener("beforeunload", function onUnload() { michael@0: contentWindow.removeEventListener("beforeunload", onUnload, false); michael@0: self.destroy(id, true); michael@0: }, false); michael@0: michael@0: // if the visualizer for the current tab is already open, destroy it now michael@0: if (this.visualizers[id]) { michael@0: this.destroy(id, true); michael@0: this._telemetry.toolClosed("tilt"); michael@0: return; michael@0: } else { michael@0: this._telemetry.toolOpened("tilt"); michael@0: } michael@0: michael@0: // create a visualizer instance for the current tab michael@0: this.visualizers[id] = new TiltVisualizer({ michael@0: chromeWindow: this.chromeWindow, michael@0: contentWindow: contentWindow, michael@0: parentNode: this.chromeWindow.gBrowser.selectedBrowser.parentNode, michael@0: notifications: this.NOTIFICATIONS, michael@0: tab: this.chromeWindow.gBrowser.selectedTab michael@0: }); michael@0: michael@0: Services.obs.notifyObservers(contentWindow, TILT_NOTIFICATIONS.STARTUP, null); michael@0: this.visualizers[id].init(); michael@0: michael@0: // make sure the visualizer object was initialized properly michael@0: if (!this.visualizers[id].isInitialized()) { michael@0: this.destroy(id); michael@0: this.failureCallback && this.failureCallback(); michael@0: return; michael@0: } michael@0: michael@0: this.lastInstanceId = id; michael@0: this.emit("change", this.chromeWindow.gBrowser.selectedTab); michael@0: Services.obs.notifyObservers(contentWindow, TILT_NOTIFICATIONS.INITIALIZING, null); michael@0: }, michael@0: michael@0: /** michael@0: * Starts destroying a specific instance of the visualizer. michael@0: * michael@0: * @param {String} aId michael@0: * the identifier of the instance in the visualizers array michael@0: * @param {Boolean} aAnimateFlag michael@0: * optional, set to true to display a destruction transition michael@0: */ michael@0: destroy: function T_destroy(aId, aAnimateFlag) michael@0: { michael@0: // if the visualizer is destroyed or destroying, don't do anything michael@0: if (!this.visualizers[aId] || this._isDestroying) { michael@0: return; michael@0: } michael@0: this._isDestroying = true; michael@0: michael@0: let controller = this.visualizers[aId].controller; michael@0: let presenter = this.visualizers[aId].presenter; michael@0: michael@0: let content = presenter.contentWindow; michael@0: let pageXOffset = content.pageXOffset * presenter.transforms.zoom; michael@0: let pageYOffset = content.pageYOffset * presenter.transforms.zoom; michael@0: TiltUtils.setDocumentZoom(this.chromeWindow, presenter.transforms.zoom); michael@0: michael@0: // if we're not doing any outro animation, just finish destruction directly michael@0: if (!aAnimateFlag) { michael@0: this._finish(aId); michael@0: return; michael@0: } michael@0: michael@0: // otherwise, trigger the outro animation and notify necessary observers michael@0: Services.obs.notifyObservers(content, TILT_NOTIFICATIONS.DESTROYING, null); michael@0: michael@0: controller.removeEventListeners(); michael@0: controller.arcball.reset([-pageXOffset, -pageYOffset]); michael@0: presenter.executeDestruction(this._finish.bind(this, aId)); michael@0: }, michael@0: michael@0: /** michael@0: * Finishes detroying a specific instance of the visualizer. michael@0: * michael@0: * @param {String} aId michael@0: * the identifier of the instance in the visualizers array michael@0: */ michael@0: _finish: function T__finish(aId) michael@0: { michael@0: let contentWindow = this.visualizers[aId].presenter.contentWindow; michael@0: this.visualizers[aId].removeOverlay(); michael@0: this.visualizers[aId].cleanup(); michael@0: this.visualizers[aId] = null; michael@0: michael@0: this._isDestroying = false; michael@0: this.chromeWindow.gBrowser.selectedBrowser.focus(); michael@0: this.emit("change", this.chromeWindow.gBrowser.selectedTab); michael@0: Services.obs.notifyObservers(contentWindow, TILT_NOTIFICATIONS.DESTROYED, null); michael@0: }, michael@0: michael@0: /** michael@0: * Handles the event fired when a tab is selected. michael@0: */ michael@0: _onTabSelect: function T__onTabSelect() michael@0: { michael@0: if (this.visualizers[this.lastInstanceId]) { michael@0: let contentWindow = this.visualizers[this.lastInstanceId].presenter.contentWindow; michael@0: Services.obs.notifyObservers(contentWindow, TILT_NOTIFICATIONS.HIDDEN, null); michael@0: } michael@0: michael@0: if (this.currentInstance) { michael@0: let contentWindow = this.currentInstance.presenter.contentWindow; michael@0: Services.obs.notifyObservers(contentWindow, TILT_NOTIFICATIONS.SHOWN, null); michael@0: } michael@0: michael@0: this.lastInstanceId = this.currentWindowId; michael@0: }, michael@0: michael@0: /** michael@0: * Add the browser event listeners to handle state changes. michael@0: */ michael@0: setup: function T_setup() michael@0: { michael@0: // load the preferences from the devtools.tilt branch michael@0: TiltVisualizer.Prefs.load(); michael@0: michael@0: this.chromeWindow.gBrowser.tabContainer.addEventListener( michael@0: "TabSelect", this._onTabSelect.bind(this), false); michael@0: }, michael@0: michael@0: /** michael@0: * Returns true if this tool is enabled. michael@0: */ michael@0: get enabled() michael@0: { michael@0: return (TiltVisualizer.Prefs.enabled && michael@0: (TiltGL.isWebGLForceEnabled() || TiltGL.isWebGLSupported())); michael@0: }, michael@0: michael@0: /** michael@0: * Gets the ID of the current window object to identify the visualizer. michael@0: */ michael@0: get currentWindowId() michael@0: { michael@0: return TiltUtils.getWindowId( michael@0: this.chromeWindow.gBrowser.selectedBrowser.contentWindow); michael@0: }, michael@0: michael@0: /** michael@0: * Gets the visualizer instance for the current tab. michael@0: */ michael@0: get currentInstance() michael@0: { michael@0: return this.visualizers[this.currentWindowId]; michael@0: }, michael@0: };