Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Spin off LessChrome HD from Home Dash.

Show less chrome and more page except tabs.
  • Loading branch information...
commit b366e1f8d871d753c004284d53fa2a28ca4b32b3 1 parent 283e1fc
@Mardak Mardak authored
View
316 lessChromeHD/bootstrap.js
@@ -0,0 +1,316 @@
+/* ***** 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 LessChrome HD.
+ *
+ * The Initial Developer of the Original Code is The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Edward Lee <edilee@mozilla.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 ***** */
+
+"use strict";
+const global = this;
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+Cu.import("resource://gre/modules/AddonManager.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+// Fix up the current window state and get ready to hide chrome
+function prepareLessChrome(window) {
+ let {async, change} = makeWindowHelpers(window);
+ let {document, gBrowser, gNavToolbox} = window;
+
+ // Make sure tabs are on top
+ change(window.TabsOnTop, "enabled", true);
+
+ // Always don't hide chrome by collapsing
+ change(window.XULBrowserWindow, "hideChromeForLocation", function(orig) {
+ return function() false;
+ });
+
+ // Make sure the current tab didn't cause chrome to hide
+ document.documentElement.removeAttribute("disablechrome");
+
+ // Figure out how much to shift the main browser
+ let TabsBar = document.getElementById("TabsToolbar");
+ gBrowser.style.marginTop = TabsBar.boxObject.y + TabsBar.boxObject.height -
+ gBrowser.parentNode.boxObject.y + "px";
+
+ // Hide toolbars by changing the height and keep it above content
+ gNavToolbox.style.overflow = "hidden";
+ gNavToolbox.style.position = "relative";
+
+ // Reset the UI to what it looked like before activating
+ unload(function() {
+ gBrowser.style.marginTop = "";
+ gNavToolbox.style.height = "";
+ gNavToolbox.style.marginBottom = "";
+ gNavToolbox.style.overflow = "";
+ gNavToolbox.style.position = "";
+ });
+
+ // Keep track of various states and modifiers of events
+ let hidden = false;
+ let ignoreMouse = false;
+ let keepOpen = false;
+ let popupOpen = false;
+ let skipClick = false;
+
+ // Show the chrome immediately
+ function show() {
+ // Prevent any pending hides now that we want to show
+ cancelDelayed();
+
+ // Nothing to do if already showing
+ if (!hidden)
+ return;
+
+ // Stop any in-progress animations
+ cancelShifter();
+ hidden = false;
+
+ // Show the full height without any filler height
+ gNavToolbox.style.height = gNavToolbox.scrollHeight + "px";
+ gNavToolbox.style.marginBottom = 0;
+ }
+
+ // Hide the chrome by animating away the non-tabs toolbar area
+ function hide() {
+ // Don't bother hiding if already hidden or showing nothing
+ if (hidden || keepOpen || showingNothing())
+ return;
+
+ // Stop any previous animations before starting another
+ cancelShifter();
+ hidden = true;
+
+ // Figure out how tall various pieces are
+ let total = gNavToolbox.scrollHeight;
+ let tabs = TabsBar.boxObject.height;
+ let other = total - tabs;
+
+ // Keep track of the animation progress
+ let startTime = Date.now();
+
+ // Do all steps on a timer so that show-hide-show won't flicker
+ (function shiftStep() shifter = async(function() {
+ // Start a little slow then speed up
+ let step = Math.pow(Math.min(1, (Date.now() - startTime) / 150), 1.5);
+
+ // Shrink the visible height while maintaining the overall height
+ gNavToolbox.style.height = tabs + other * (1 - step) + "px";
+ gNavToolbox.style.marginBottom = other * step + "px";
+
+ // Prepare the next step of the animation
+ if (step < 1)
+ shiftStep();
+ // Otherwise we're done!
+ else
+ shifter = null;
+ }))();
+ }
+
+ // Clicking the page content dismisses the chrome
+ listen(window, gBrowser, "click", function({button}) {
+ if (button != 0)
+ return;
+
+ hide();
+ });
+
+ // Typing in the page content dismisses the chrome
+ listen(window, gBrowser, "keypress", function() {
+ hide();
+ });
+
+ // Moving the mouse down into content can hide the chrome
+ listen(window, gBrowser, "mousemove", function({clientY}) {
+ // Allow clicks to toggle now that it moved away to content
+ skipClick = false;
+
+ // Keep ignoring mouse moves unless moving more than slightly away
+ if (ignoreMouse && clientY > 30)
+ ignoreMouse = false;
+
+ // Don't bother hiding if it shouldn't hide now
+ if (hidden || popupOpen || keepOpen)
+ return;
+
+ // Only hide if the mouse moves far down enough
+ if (clientY > gNavToolbox.boxObject.height + 30)
+ hide();
+ });
+
+ // Show some context when switching tabs
+ listen(window, gBrowser.tabContainer, "TabSelect", function({target}) {
+ // Avoid toggling if a tab was clicked to select
+ skipClick = true;
+
+ // Immediately show the chrome for context on tab switch
+ show();
+
+ // Force the chrome to stay visible in-case chrome blurred
+ async(function() {
+ show();
+
+ // Wait a few seconds before hiding the url/security context
+ delayedHide = async(function() {
+ hide();
+ delayedHide = null;
+ }, 3000);
+ });
+ });
+
+ // Hide the chrome when potentially moving focus to content
+ listen(window, gNavToolbox, "blur", function() {
+ // Start the hide animation now, and an immediate focus will cancel
+ keepOpen = false;
+ hide();
+ });
+
+ // Detect focus events for the location bar, etc. to show chrome
+ listen(window, gNavToolbox, "focus", function() {
+ // Make sure to keep the chrome available even when pointing away
+ keepOpen = true;
+ show();
+ });
+
+ // Allow toggling the chrome when clicking the tabs area
+ listen(window, TabsBar, "click", function({button}) {
+ // Make sure to ignore movement after potentially hiding
+ ignoreMouse = true;
+
+ // Only handle primary clicks and ignore one click if necessary
+ if (button != 0 || skipClick) {
+ skipClick = false;
+ return;
+ }
+
+ // Toggle to the other state
+ if (hidden)
+ show();
+ else
+ hide();
+ });
+
+ // Show chrome when the mouse moves over the tabs
+ listen(window, TabsBar, "mousemove", function() {
+ // Don't show after the tabs area was clicked
+ if (ignoreMouse)
+ return;
+
+ show();
+ });
+
+ // Any mouse scrolls hide the chrome
+ listen(window, window, "DOMMouseScroll", function() {
+ // Allow scrolling on tabs area to hide without reshowing
+ ignoreMouse = true
+ hide();
+ });
+
+ // Remember when the popup hides to allow events to resume
+ listen(window, window, "popuphiding", function() {
+ popupOpen = false;
+ });
+
+ // Show chrome with the context menu appearing
+ listen(window, window, "popupshowing", function({target}) {
+ // Ignore some kinds of popups
+ if (target.nodeName.search(/(page|select|tooltip|window)$/i) == 0 ||
+ target.id.search(/(PopupAutoComplete)$/i) == 0) {
+ return;
+ }
+
+ // Prevent various events when the popup is open
+ popupOpen = true;
+ skipClick = true;
+
+ show();
+ });
+
+ // Keep references to various timers and provide helpers to cancel them
+ let delayedHide;
+ function cancelDelayed() {
+ if (delayedHide == null)
+ return;
+
+ delayedHide();
+ delayedHide = null;
+ }
+
+ let shifter;
+ function cancelShifter() {
+ if (shifter == null)
+ return;
+
+ shifter();
+ shifter = null;
+ }
+
+ // Check if the current tab is blank
+ function showingNothing() {
+ return gBrowser.selectedBrowser.currentURI.spec == "about:blank";
+ }
+}
+
+/**
+ * Handle the add-on being activated on install/enable
+ */
+function startup({id}) AddonManager.getAddonByID(id, function(addon) {
+ // Load various javascript includes for helper functions
+ ["helper", "utils"].forEach(function(fileName) {
+ let fileURI = addon.getResourceURI("scripts/" + fileName + ".js");
+ Services.scriptloader.loadSubScript(fileURI.spec, global);
+ });
+
+ // Get ready to hide some of the chrome!
+ watchWindows(prepareLessChrome);
+})
+
+
+/**
+ * Handle the add-on being deactivated on uninstall/disable
+ */
+function shutdown(data, reason) {
+ // Clean up with unloaders when we're deactivating
+ if (reason != APP_SHUTDOWN)
+ unload();
+}
+
+/**
+ * Handle the add-on being installed
+ */
+function install(data, reason) {}
+
+/**
+ * Handle the add-on being uninstalled
+ */
+function uninstall(data, reason) {}
View
24 lessChromeHD/install.rdf
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<r:RDF xmlns="http://www.mozilla.org/2004/em-rdf#"
+ xmlns:r="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+ <r:Description about="urn:mozilla:install-manifest">
+ <creator>Mozilla Labs</creator>
+ <description>Show less chrome by default unless it's needed</description>
+ <homepageURL>https://mozillalabs.com/prospector</homepageURL>
+ <iconURL>http://mozillalabs.com/wp-content/themes/labs_project/img/prospector-header.png</iconURL>
+ <id>lessChrome.HD@prospector.labs.mozilla</id>
+ <name>Mozilla Labs: Prospector - LessChrome HD</name>
+ <version>1</version>
+
+ <bootstrap>true</bootstrap>
+ <type>2</type>
+
+ <targetApplication>
+ <r:Description>
+ <id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</id>
+ <minVersion>4.0</minVersion>
+ <maxVersion>6.0a1</maxVersion>
+ </r:Description>
+ </targetApplication>
+ </r:Description>
+</r:RDF>
View
80 lessChromeHD/scripts/helper.js
@@ -0,0 +1,80 @@
+/* ***** 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 Home Dash Helper Functions.
+ *
+ * The Initial Developer of the Original Code is The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Edward Lee <edilee@mozilla.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 ***** */
+
+"use strict";
+
+const isMac = Services.appinfo.OS == "Darwin";
+const isWin = Services.appinfo.OS == "WINNT";
+
+// Take a window and create various helper properties and functions
+function makeWindowHelpers(window) {
+ let {clearTimeout, setTimeout} = window;
+
+ // Call a function after waiting a little bit
+ function async(callback, delay) {
+ let timer = setTimeout(function() {
+ stopTimer();
+ callback();
+ }, delay);
+
+ // Provide a way to stop an active timer
+ function stopTimer() {
+ if (timer == null)
+ return;
+ clearTimeout(timer);
+ timer = null;
+ unUnload();
+ }
+
+ // Make sure to stop the timer when unloading
+ let unUnload = unload(stopTimer, window);
+
+ // Give the caller a way to cancel the timer
+ return stopTimer;
+ }
+
+ // Replace a value with another value or a function of the original value
+ function change(obj, prop, val) {
+ let orig = obj[prop];
+ obj[prop] = typeof val == "function" ? val(orig) : val;
+ unload(function() obj[prop] = orig, window);
+ }
+
+ return {
+ async: async,
+ change: change,
+ };
+}
View
310 lessChromeHD/scripts/utils.js
@@ -0,0 +1,310 @@
+/* ***** 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 Home Dash Utility.
+ *
+ * The Initial Developer of the Original Code is The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Edward Lee <edilee@mozilla.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 ***** */
+
+"use strict";
+
+/**
+ * Get a localized string with string replacement arguments filled in and
+ * correct plural form picked if necessary.
+ *
+ * @note: Initialize the strings to use with getString.init(addon).
+ *
+ * @usage getString(name): Get the localized string for the given name.
+ * @param [string] name: Corresponding string name in the properties file.
+ * @return [string]: Localized string for the string name.
+ *
+ * @usage getString(name, arg): Replace %S references in the localized string.
+ * @param [string] name: Corresponding string name in the properties file.
+ * @param [any] arg: Value to insert for instances of %S.
+ * @return [string]: Localized string with %S references replaced.
+ *
+ * @usage getString(name, args): Replace %1$S references in localized string.
+ * @param [string] name: Corresponding string name in the properties file.
+ * @param [array of any] args: Array of values to replace references like %1$S.
+ * @return [string]: Localized string with %N$S references replaced.
+ *
+ * @usage getString(name, args, plural): Pick the correct plural form.
+ * @param [string] name: Corresponding string name in the properties file.
+ * @param [array of any] args: Array of values to replace references like %1$S.
+ * @param [number] plural: Number to decide what plural form to use.
+ * @return [string]: Localized string of the correct plural form.
+ */
+function getString(name, args, plural) {
+ // Use the cached bundle to retrieve the string
+ let str;
+ try {
+ str = getString.bundle.GetStringFromName(name);
+ }
+ // Use the fallback in-case the string isn't localized
+ catch(ex) {
+ str = getString.fallback.GetStringFromName(name);
+ }
+
+ // Pick out the correct plural form if necessary
+ if (plural != null)
+ str = getString.plural(plural, str);
+
+ // Fill in the arguments if necessary
+ if (args != null) {
+ // Convert a string or something not array-like to an array
+ if (typeof args == "string" || args.length == null)
+ args = [args];
+
+ // Assume %S refers to the first argument
+ str = str.replace(/%s/gi, args[0]);
+
+ // Replace instances of %N$S where N is a 1-based number
+ Array.forEach(args, function(replacement, index) {
+ str = str.replace(RegExp("%" + (index + 1) + "\\$S", "gi"), replacement);
+ });
+ }
+
+ return str;
+}
+
+/**
+ * Initialize getString() for the provided add-on.
+ *
+ * @usage getString.init(addon): Load properties file for the add-on.
+ * @param [object] addon: Add-on object from AddonManager
+ *
+ * @usage getString.init(addon, getAlternate): Load properties with alternate.
+ * @param [object] addon: Add-on object from AddonManager
+ * @param [function] getAlternate: Convert a locale to an alternate locale
+ */
+getString.init = function(addon, getAlternate) {
+ // Set a default get alternate function if it doesn't exist
+ if (typeof getAlternate != "function")
+ getAlternate = function() "en-US";
+
+ // Get the bundled properties file for the app's locale
+ function getBundle(locale) {
+ let propertyPath = "locales/" + locale + ".properties";
+ let propertyFile = addon.getResourceURI(propertyPath);
+
+ // Get a bundle and test if it's able to do simple things
+ try {
+ // Avoid caching issues by always getting a new file
+ let uniqueFileSpec = propertyFile.spec + "#" + Math.random();
+ let bundle = Services.strings.createBundle(uniqueFileSpec);
+ bundle.getSimpleEnumeration();
+ return bundle;
+ }
+ catch(ex) {}
+
+ // The locale must not exist, so give nothing
+ return null;
+ }
+
+ // Use the current locale or the alternate as the primary bundle
+ let locale = Cc["@mozilla.org/chrome/chrome-registry;1"].
+ getService(Ci.nsIXULChromeRegistry).getSelectedLocale("global");
+ getString.bundle = getBundle(locale) || getBundle(getAlternate(locale));
+
+ // Create a fallback in-case a string is missing
+ getString.fallback = getBundle("en-US");
+
+ // Get the appropriate plural form getter
+ Cu.import("resource://gre/modules/PluralForm.jsm");
+ let rule = getString("pluralRule");
+ [getString.plural] = PluralForm.makeGetter(rule);
+}
+
+/**
+ * Create a trigger that allows adding callbacks by default then triggering all
+ * of them.
+ */
+function makeTrigger() {
+ let callbacks = [];
+
+ // Provide the main function to add callbacks that can be removed
+ function addCallback(callback) {
+ callbacks.push(callback);
+ return function() {
+ let index = callbacks.indexOf(callback);
+ if (index != -1)
+ callbacks.splice(index, 1);
+ };
+ }
+
+ // Provide a way to clear out all the callbacks
+ addCallback.reset = function() {
+ callbacks.length = 0;
+ };
+
+ // Run each callback in order ignoring failures
+ addCallback.trigger = function(reason) {
+ callbacks.slice().forEach(function(callback) {
+ try {
+ callback(reason);
+ }
+ catch(ex) {}
+ });
+ };
+
+ return addCallback;
+}
+
+/**
+ * Apply a callback to each open and new browser windows.
+ *
+ * @usage watchWindows(callback): Apply a callback to each browser window.
+ * @param [function] callback: 1-parameter function that gets a browser window.
+ */
+function watchWindows(callback) {
+ // Wrap the callback in a function that ignores failures
+ function watcher(window) {
+ try {
+ callback(window);
+ }
+ catch(ex) {}
+ }
+
+ // Wait for the window to finish loading before running the callback
+ function runOnLoad(window) {
+ // Listen for one load event before checking the window type
+ window.addEventListener("load", function runOnce() {
+ window.removeEventListener("load", runOnce, false);
+
+ // Now that the window has loaded, only handle browser windows
+ let doc = window.document.documentElement;
+ if (doc.getAttribute("windowtype") == "navigator:browser")
+ watcher(window);
+ }, false);
+ }
+
+ // Add functionality to existing windows
+ let browserWindows = Services.wm.getEnumerator("navigator:browser");
+ while (browserWindows.hasMoreElements()) {
+ // Only run the watcher immediately if the browser is completely loaded
+ let browserWindow = browserWindows.getNext();
+ if (browserWindow.document.readyState == "complete")
+ watcher(browserWindow);
+ // Wait for the window to load before continuing
+ else
+ runOnLoad(browserWindow);
+ }
+
+ // Watch for new browser windows opening then wait for it to load
+ function windowWatcher(subject, topic) {
+ if (topic == "domwindowopened")
+ runOnLoad(subject);
+ }
+ Services.ww.registerNotification(windowWatcher);
+
+ // Make sure to stop watching for windows if we're unloading
+ unload(function() Services.ww.unregisterNotification(windowWatcher));
+}
+
+/**
+ * Save callbacks to run when unloading. Optionally scope the callback to a
+ * container, e.g., window. Provide a way to run all the callbacks.
+ *
+ * @usage unload(): Run all callbacks and release them.
+ *
+ * @usage unload(callback): Add a callback to run on unload.
+ * @param [function] callback: 0-parameter function to call on unload.
+ * @return [function]: A 0-parameter function that undoes adding the callback.
+ *
+ * @usage unload(callback, container) Add a scoped callback to run on unload.
+ * @param [function] callback: 0-parameter function to call on unload.
+ * @param [node] container: Remove the callback when this container unloads.
+ * @return [function]: A 0-parameter function that undoes adding the callback.
+ */
+function unload(callback, container) {
+ // Initialize the array of unloaders on the first usage
+ let unloaders = unload.unloaders;
+ if (unloaders == null)
+ unloaders = unload.unloaders = [];
+
+ // Calling with no arguments runs all the unloader callbacks
+ if (callback == null) {
+ unloaders.slice().forEach(function(unloader) unloader());
+ unloaders.length = 0;
+ return;
+ }
+
+ // The callback is bound to the lifetime of the container if we have one
+ if (container != null) {
+ // Remove the unloader when the container unloads
+ container.addEventListener("unload", removeUnloader, false);
+
+ // Wrap the callback to additionally remove the unload listener
+ let origCallback = callback;
+ callback = function() {
+ container.removeEventListener("unload", removeUnloader, false);
+ origCallback();
+ }
+ }
+
+ // Wrap the callback in a function that ignores failures
+ function unloader() {
+ try {
+ callback();
+ }
+ catch(ex) {}
+ }
+ unloaders.push(unloader);
+
+ // Provide a way to remove the unloader
+ function removeUnloader() {
+ let index = unloaders.indexOf(unloader);
+ if (index != -1)
+ unloaders.splice(index, 1);
+ }
+ return removeUnloader;
+}
+
+/**
+ * Helper that adds event listeners and remembers to remove on unload
+ */
+function listen(window, node, event, func, capture) {
+ // Default to use capture
+ if (capture == null)
+ capture = true;
+
+ node.addEventListener(event, func, capture);
+ function undoListen() {
+ node.removeEventListener(event, func, capture);
+ }
+
+ // Undo the listener on unload and provide a way to undo everything
+ let undoUnload = unload(undoListen, window);
+ return function() {
+ undoListen();
+ undoUnload();
+ };
+}
Please sign in to comment.
Something went wrong with that request. Please try again.