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