forked from mirrors/gecko-dev
		
	If there are no stored logins, the Cancel button is hidden. If there are visible stored logins, clicking the Cancel button will exit and move to the first login in the list. If there are no visible stored logins, clicking the Cancel button will reset the search filter, exit, and move to the first login in the list. This patch also fixes a race condition where we could end up with multiple observer notifications if about:logins was loaded multiple times very quickly. Differential Revision: https://phabricator.services.mozilla.com/D43544 --HG-- extra : moz-landing-system : lando
		
			
				
	
	
		
			211 lines
		
	
	
	
		
			6.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			211 lines
		
	
	
	
		
			6.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* 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/. */
 | 
						|
 | 
						|
/*
 | 
						|
 * This module implements a number of utility functions that can be loaded
 | 
						|
 * into content scope.
 | 
						|
 *
 | 
						|
 * All asynchronous helper methods should return promises, rather than being
 | 
						|
 * callback based.
 | 
						|
 */
 | 
						|
 | 
						|
// Disable ownerGlobal use since that's not available on content-privileged elements.
 | 
						|
 | 
						|
/* eslint-disable mozilla/use-ownerGlobal */
 | 
						|
 | 
						|
"use strict";
 | 
						|
 | 
						|
var EXPORTED_SYMBOLS = ["ContentTaskUtils"];
 | 
						|
 | 
						|
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 | 
						|
const { clearInterval, setInterval, setTimeout } = ChromeUtils.import(
 | 
						|
  "resource://gre/modules/Timer.jsm"
 | 
						|
);
 | 
						|
 | 
						|
var ContentTaskUtils = {
 | 
						|
  /**
 | 
						|
   * Checks if a DOM element is hidden.
 | 
						|
   *
 | 
						|
   * @param {Element} element
 | 
						|
   *        The element which is to be checked.
 | 
						|
   *
 | 
						|
   * @return {boolean}
 | 
						|
   */
 | 
						|
  is_hidden(element) {
 | 
						|
    let style = element.ownerDocument.defaultView.getComputedStyle(element);
 | 
						|
    if (style.display == "none") {
 | 
						|
      return true;
 | 
						|
    }
 | 
						|
    if (style.visibility != "visible") {
 | 
						|
      return true;
 | 
						|
    }
 | 
						|
 | 
						|
    // Hiding a parent element will hide all its children
 | 
						|
    if (
 | 
						|
      element.parentNode != element.ownerDocument &&
 | 
						|
      element.parentNode.nodeType != Node.DOCUMENT_FRAGMENT_NODE
 | 
						|
    ) {
 | 
						|
      return ContentTaskUtils.is_hidden(element.parentNode);
 | 
						|
    }
 | 
						|
 | 
						|
    // Walk up the shadow DOM if we've reached the top of the shadow root
 | 
						|
    if (element.parentNode.host) {
 | 
						|
      return ContentTaskUtils.is_hidden(element.parentNode.host);
 | 
						|
    }
 | 
						|
 | 
						|
    return false;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Checks if a DOM element is visible.
 | 
						|
   *
 | 
						|
   * @param {Element} element
 | 
						|
   *        The element which is to be checked.
 | 
						|
   *
 | 
						|
   * @return {boolean}
 | 
						|
   */
 | 
						|
  is_visible(element) {
 | 
						|
    return !this.is_hidden(element);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Will poll a condition function until it returns true.
 | 
						|
   *
 | 
						|
   * @param condition
 | 
						|
   *        A condition function that must return true or false. If the
 | 
						|
   *        condition ever throws, this is also treated as a false.
 | 
						|
   * @param msg
 | 
						|
   *        The message to use when the returned promise is rejected.
 | 
						|
   *        This message will be extended with additional information
 | 
						|
   *        about the number of tries or the thrown exception.
 | 
						|
   * @param interval
 | 
						|
   *        The time interval to poll the condition function. Defaults
 | 
						|
   *        to 100ms.
 | 
						|
   * @param maxTries
 | 
						|
   *        The number of times to poll before giving up and rejecting
 | 
						|
   *        if the condition has not yet returned true. Defaults to 50
 | 
						|
   *        (~5 seconds for 100ms intervals)
 | 
						|
   * @return Promise
 | 
						|
   *        Resolves when condition is true.
 | 
						|
   *        Rejects if timeout is exceeded or condition ever throws.
 | 
						|
   */
 | 
						|
  waitForCondition(condition, msg, interval = 100, maxTries = 50) {
 | 
						|
    return new Promise((resolve, reject) => {
 | 
						|
      let tries = 0;
 | 
						|
      let intervalID = setInterval(() => {
 | 
						|
        if (tries >= maxTries) {
 | 
						|
          clearInterval(intervalID);
 | 
						|
          msg += ` - timed out after ${maxTries} tries.`;
 | 
						|
          reject(msg);
 | 
						|
          return;
 | 
						|
        }
 | 
						|
 | 
						|
        let conditionPassed = false;
 | 
						|
        try {
 | 
						|
          conditionPassed = condition();
 | 
						|
        } catch (e) {
 | 
						|
          msg += ` - threw exception: ${e}`;
 | 
						|
          clearInterval(intervalID);
 | 
						|
          reject(msg);
 | 
						|
          return;
 | 
						|
        }
 | 
						|
 | 
						|
        if (conditionPassed) {
 | 
						|
          clearInterval(intervalID);
 | 
						|
          resolve(conditionPassed);
 | 
						|
        }
 | 
						|
        tries++;
 | 
						|
      }, interval);
 | 
						|
    });
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Waits for an event to be fired on a specified element.
 | 
						|
   *
 | 
						|
   * Usage:
 | 
						|
   *    let promiseEvent = ContentTasKUtils.waitForEvent(element, "eventName");
 | 
						|
   *    // Do some processing here that will cause the event to be fired
 | 
						|
   *    // ...
 | 
						|
   *    // Now yield until the Promise is fulfilled
 | 
						|
   *    let receivedEvent = yield promiseEvent;
 | 
						|
   *
 | 
						|
   * @param {Element} subject
 | 
						|
   *        The element that should receive the event.
 | 
						|
   * @param {string} eventName
 | 
						|
   *        Name of the event to listen to.
 | 
						|
   * @param {bool} capture [optional]
 | 
						|
   *        True to use a capturing listener.
 | 
						|
   * @param {function} checkFn [optional]
 | 
						|
   *        Called with the Event object as argument, should return true if the
 | 
						|
   *        event is the expected one, or false if it should be ignored and
 | 
						|
   *        listening should continue. If not specified, the first event with
 | 
						|
   *        the specified name resolves the returned promise.
 | 
						|
   *
 | 
						|
   * @note Because this function is intended for testing, any error in checkFn
 | 
						|
   *       will cause the returned promise to be rejected instead of waiting for
 | 
						|
   *       the next event, since this is probably a bug in the test.
 | 
						|
   *
 | 
						|
   * @returns {Promise}
 | 
						|
   * @resolves The Event object.
 | 
						|
   */
 | 
						|
  waitForEvent(subject, eventName, capture, checkFn, wantsUntrusted = false) {
 | 
						|
    return new Promise((resolve, reject) => {
 | 
						|
      subject.addEventListener(
 | 
						|
        eventName,
 | 
						|
        function listener(event) {
 | 
						|
          try {
 | 
						|
            if (checkFn && !checkFn(event)) {
 | 
						|
              return;
 | 
						|
            }
 | 
						|
            subject.removeEventListener(eventName, listener, capture);
 | 
						|
            setTimeout(() => resolve(event), 0);
 | 
						|
          } catch (ex) {
 | 
						|
            try {
 | 
						|
              subject.removeEventListener(eventName, listener, capture);
 | 
						|
            } catch (ex2) {
 | 
						|
              // Maybe the provided object does not support removeEventListener.
 | 
						|
            }
 | 
						|
            setTimeout(() => reject(ex), 0);
 | 
						|
          }
 | 
						|
        },
 | 
						|
        capture,
 | 
						|
        wantsUntrusted
 | 
						|
      );
 | 
						|
    });
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Gets an instance of the `EventUtils` helper module for usage in
 | 
						|
   * content tasks. See https://searchfox.org/mozilla-central/source/testing/mochitest/tests/SimpleTest/EventUtils.js
 | 
						|
   *
 | 
						|
   * @param content
 | 
						|
   *        The `content` global object from your content task.
 | 
						|
   *
 | 
						|
   * @returns an EventUtils instance.
 | 
						|
   */
 | 
						|
  getEventUtils(content) {
 | 
						|
    if (content._EventUtils) {
 | 
						|
      return content._EventUtils;
 | 
						|
    }
 | 
						|
 | 
						|
    let EventUtils = (content._EventUtils = {});
 | 
						|
 | 
						|
    EventUtils.window = {};
 | 
						|
    EventUtils.parent = EventUtils.window;
 | 
						|
    /* eslint-disable camelcase */
 | 
						|
    EventUtils._EU_Ci = Ci;
 | 
						|
    EventUtils._EU_Cc = Cc;
 | 
						|
    /* eslint-enable camelcase */
 | 
						|
    // EventUtils' `sendChar` function relies on the navigator to synthetize events.
 | 
						|
    EventUtils.navigator = content.navigator;
 | 
						|
    EventUtils.KeyboardEvent = content.KeyboardEvent;
 | 
						|
 | 
						|
    Services.scriptloader.loadSubScript(
 | 
						|
      "chrome://mochikit/content/tests/SimpleTest/EventUtils.js",
 | 
						|
      EventUtils
 | 
						|
    );
 | 
						|
 | 
						|
    return EventUtils;
 | 
						|
  },
 | 
						|
};
 |