mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 02:09:05 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			424 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			424 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
const { Cc, Ci, Cu: ChromeUtils } = SpecialPowers;
 | 
						|
 | 
						|
/**
 | 
						|
 * Converts a property bag to object.
 | 
						|
 * @param {nsIPropertyBag} bag - The property bag to convert
 | 
						|
 * @returns {Object} - The object representation of the nsIPropertyBag
 | 
						|
 */
 | 
						|
function propBagToObject(bag) {
 | 
						|
  if (!(bag instanceof Ci.nsIPropertyBag)) {
 | 
						|
    throw new TypeError("Not a property bag");
 | 
						|
  }
 | 
						|
  let result = {};
 | 
						|
  for (let { name, value } of bag.enumerator) {
 | 
						|
    result[name] = value;
 | 
						|
  }
 | 
						|
  return result;
 | 
						|
}
 | 
						|
 | 
						|
var modalType;
 | 
						|
var isSelectDialog = false;
 | 
						|
var isOSX = "nsILocalFileMac" in SpecialPowers.Ci;
 | 
						|
var isE10S = SpecialPowers.Services.appinfo.processType == 2;
 | 
						|
 | 
						|
var gChromeScript = SpecialPowers.loadChromeScript(
 | 
						|
  SimpleTest.getTestFileURL("chromeScript.js")
 | 
						|
);
 | 
						|
SimpleTest.registerCleanupFunction(() => gChromeScript.destroy());
 | 
						|
 | 
						|
async function runPromptCombinations(window, testFunc) {
 | 
						|
  let util = new PromptTestUtil(window);
 | 
						|
  let run = () => {
 | 
						|
    info(
 | 
						|
      `Running tests (modalType=${modalType}, usePromptService=${util.usePromptService}, useBrowsingContext=${util.useBrowsingContext}, useAsync=${util.useAsync})`
 | 
						|
    );
 | 
						|
    return testFunc(util);
 | 
						|
  };
 | 
						|
 | 
						|
  // Prompt service with dom window parent only supports window prompts
 | 
						|
  util.usePromptService = true;
 | 
						|
  util.useBrowsingContext = false;
 | 
						|
  util.modalType = Ci.nsIPrompt.MODAL_TYPE_WINDOW;
 | 
						|
  modalType = util.modalType;
 | 
						|
  util.useAsync = false;
 | 
						|
  await run();
 | 
						|
 | 
						|
  let modalTypes = [
 | 
						|
    Ci.nsIPrompt.MODAL_TYPE_WINDOW,
 | 
						|
    Ci.nsIPrompt.MODAL_TYPE_TAB,
 | 
						|
    Ci.nsIPrompt.MODAL_TYPE_CONTENT,
 | 
						|
  ];
 | 
						|
 | 
						|
  for (let type of modalTypes) {
 | 
						|
    util.modalType = type;
 | 
						|
    modalType = type;
 | 
						|
 | 
						|
    // Prompt service with browsing context sync
 | 
						|
    util.usePromptService = true;
 | 
						|
    util.useBrowsingContext = true;
 | 
						|
    util.useAsync = false;
 | 
						|
    await run();
 | 
						|
 | 
						|
    // Prompt service with browsing context async
 | 
						|
    util.usePromptService = true;
 | 
						|
    util.useBrowsingContext = true;
 | 
						|
    util.useAsync = true;
 | 
						|
    await run();
 | 
						|
 | 
						|
    // nsIPrompt
 | 
						|
    // modalType is set via nsIWritablePropertyBag (legacy)
 | 
						|
    util.usePromptService = false;
 | 
						|
    util.useBrowsingContext = false;
 | 
						|
    util.useAsync = false;
 | 
						|
    await run();
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
class PromptTestUtil {
 | 
						|
  constructor(window) {
 | 
						|
    this.window = window;
 | 
						|
    this.browsingContext =
 | 
						|
      SpecialPowers.wrap(window).windowGlobalChild.browsingContext;
 | 
						|
    this.promptService = SpecialPowers.Services.prompt;
 | 
						|
    this.nsPrompt = Cc["@mozilla.org/prompter;1"]
 | 
						|
      .getService(Ci.nsIPromptFactory)
 | 
						|
      .getPrompt(window, Ci.nsIPrompt);
 | 
						|
 | 
						|
    this.usePromptService = null;
 | 
						|
    this.useBrowsingContext = null;
 | 
						|
    this.useAsync = null;
 | 
						|
    this.modalType = null;
 | 
						|
  }
 | 
						|
 | 
						|
  get _prompter() {
 | 
						|
    if (this.usePromptService) {
 | 
						|
      return this.promptService;
 | 
						|
    }
 | 
						|
    return this.nsPrompt;
 | 
						|
  }
 | 
						|
 | 
						|
  async prompt(funcName, promptArgs) {
 | 
						|
    if (
 | 
						|
      this.useBrowsingContext == null ||
 | 
						|
      this.usePromptService == null ||
 | 
						|
      this.useAsync == null ||
 | 
						|
      this.modalType == null
 | 
						|
    ) {
 | 
						|
      throw new Error("Not initialized");
 | 
						|
    }
 | 
						|
    let args = [];
 | 
						|
    if (this.usePromptService) {
 | 
						|
      if (this.useBrowsingContext) {
 | 
						|
        if (this.useAsync) {
 | 
						|
          funcName = `async${funcName[0].toUpperCase()}${funcName.substring(
 | 
						|
            1
 | 
						|
          )}`;
 | 
						|
        } else {
 | 
						|
          funcName += "BC";
 | 
						|
        }
 | 
						|
        args = [this.browsingContext, this.modalType];
 | 
						|
      } else {
 | 
						|
        args = [this.window];
 | 
						|
      }
 | 
						|
    } else {
 | 
						|
      let bag = this.nsPrompt.QueryInterface(Ci.nsIWritablePropertyBag2);
 | 
						|
      bag.setPropertyAsUint32("modalType", this.modalType);
 | 
						|
    }
 | 
						|
    // Append the prompt arguments
 | 
						|
    args = args.concat(promptArgs);
 | 
						|
 | 
						|
    let interfaceName = this.usePromptService ? "Services.prompt" : "prompt";
 | 
						|
    ok(
 | 
						|
      this._prompter[funcName],
 | 
						|
      `${interfaceName} should have method ${funcName}.`
 | 
						|
    );
 | 
						|
 | 
						|
    info(`Calling ${interfaceName}.${funcName}(${args})`);
 | 
						|
    let result = this._prompter[funcName](...args);
 | 
						|
    is(
 | 
						|
      this.useAsync,
 | 
						|
      result != null &&
 | 
						|
        result.constructor != null &&
 | 
						|
        result.constructor.name === "Promise",
 | 
						|
      "If method is async it should return a promise."
 | 
						|
    );
 | 
						|
 | 
						|
    if (this.useAsync) {
 | 
						|
      let propBag = await result;
 | 
						|
      return propBag && propBagToObject(propBag);
 | 
						|
    }
 | 
						|
    return result;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
function onloadPromiseFor(id) {
 | 
						|
  var iframe = document.getElementById(id);
 | 
						|
  return new Promise(resolve => {
 | 
						|
    iframe.addEventListener(
 | 
						|
      "load",
 | 
						|
      function () {
 | 
						|
        resolve(true);
 | 
						|
      },
 | 
						|
      { once: true }
 | 
						|
    );
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Take an action on the next prompt that appears without checking the state in advance.
 | 
						|
 * This is useful when the action doesn't depend on which prompt is shown and you
 | 
						|
 * are expecting multiple prompts at once in an indeterminate order.
 | 
						|
 * If you know the state of the prompt you expect you should use `handlePrompt` instead.
 | 
						|
 * @param {object} action defining how to handle the prompt
 | 
						|
 * @returns {Promise} resolving with the prompt state.
 | 
						|
 */
 | 
						|
function handlePromptWithoutChecks(action) {
 | 
						|
  return new Promise(resolve => {
 | 
						|
    gChromeScript.addMessageListener("promptHandled", function handled(msg) {
 | 
						|
      gChromeScript.removeMessageListener("promptHandled", handled);
 | 
						|
      resolve(msg.promptState);
 | 
						|
    });
 | 
						|
    gChromeScript.sendAsyncMessage("handlePrompt", { action, modalType });
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
async function handlePrompt(state, action) {
 | 
						|
  let actualState = await handlePromptWithoutChecks(action);
 | 
						|
  checkPromptState(actualState, state);
 | 
						|
}
 | 
						|
 | 
						|
function checkPromptState(promptState, expectedState) {
 | 
						|
  info(`checkPromptState: Expected: ${expectedState.msg}`);
 | 
						|
  // XXX check title? OS X has title in content
 | 
						|
  is(promptState.msg, expectedState.msg, "Checking expected message");
 | 
						|
 | 
						|
  if (isOSX || promptState.isSubDialogPrompt || promptState.showCallerOrigin) {
 | 
						|
    ok(
 | 
						|
      !promptState.titleHidden,
 | 
						|
      "Checking title always visible on OS X or when opened with common dialog"
 | 
						|
    );
 | 
						|
  } else {
 | 
						|
    is(
 | 
						|
      promptState.titleHidden,
 | 
						|
      expectedState.titleHidden,
 | 
						|
      "Checking title visibility"
 | 
						|
    );
 | 
						|
  }
 | 
						|
  is(
 | 
						|
    promptState.textHidden,
 | 
						|
    expectedState.textHidden,
 | 
						|
    "Checking textbox visibility"
 | 
						|
  );
 | 
						|
  is(
 | 
						|
    promptState.passHidden,
 | 
						|
    expectedState.passHidden,
 | 
						|
    "Checking passbox visibility"
 | 
						|
  );
 | 
						|
  is(
 | 
						|
    promptState.checkHidden,
 | 
						|
    expectedState.checkHidden,
 | 
						|
    "Checking checkbox visibility"
 | 
						|
  );
 | 
						|
  is(promptState.checkMsg, expectedState.checkMsg, "Checking checkbox label");
 | 
						|
  is(promptState.checked, expectedState.checked, "Checking checkbox checked");
 | 
						|
  if (
 | 
						|
    modalType === Ci.nsIPrompt.MODAL_TYPE_WINDOW ||
 | 
						|
    modalType === Ci.nsIPrompt.MODAL_TYPE_TAB
 | 
						|
  ) {
 | 
						|
    is(
 | 
						|
      promptState.iconClass,
 | 
						|
      expectedState.iconClass,
 | 
						|
      "Checking expected icon CSS class"
 | 
						|
    );
 | 
						|
  }
 | 
						|
  is(promptState.textValue, expectedState.textValue, "Checking textbox value");
 | 
						|
  is(promptState.passValue, expectedState.passValue, "Checking passbox value");
 | 
						|
 | 
						|
  if (expectedState.butt0Label) {
 | 
						|
    is(
 | 
						|
      promptState.butt0Label,
 | 
						|
      expectedState.butt0Label,
 | 
						|
      "Checking accept-button label"
 | 
						|
    );
 | 
						|
  }
 | 
						|
  if (expectedState.butt1Label) {
 | 
						|
    is(
 | 
						|
      promptState.butt1Label,
 | 
						|
      expectedState.butt1Label,
 | 
						|
      "Checking cancel-button label"
 | 
						|
    );
 | 
						|
  }
 | 
						|
  if (expectedState.butt2Label) {
 | 
						|
    is(
 | 
						|
      promptState.butt2Label,
 | 
						|
      expectedState.butt2Label,
 | 
						|
      "Checking extra1-button label"
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  // For prompts with a time-delay button.
 | 
						|
  if (expectedState.butt0Disabled) {
 | 
						|
    is(promptState.butt0Disabled, true, "Checking accept-button is disabled");
 | 
						|
    is(
 | 
						|
      promptState.butt1Disabled,
 | 
						|
      false,
 | 
						|
      "Checking cancel-button isn't disabled"
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  is(
 | 
						|
    promptState.defButton0,
 | 
						|
    expectedState.defButton == "button0",
 | 
						|
    "checking button0 default"
 | 
						|
  );
 | 
						|
  is(
 | 
						|
    promptState.defButton1,
 | 
						|
    expectedState.defButton == "button1",
 | 
						|
    "checking button1 default"
 | 
						|
  );
 | 
						|
  is(
 | 
						|
    promptState.defButton2,
 | 
						|
    expectedState.defButton == "button2",
 | 
						|
    "checking button2 default"
 | 
						|
  );
 | 
						|
 | 
						|
  if (
 | 
						|
    isOSX &&
 | 
						|
    expectedState.focused &&
 | 
						|
    expectedState.focused.startsWith("button") &&
 | 
						|
    !promptState.infoRowHidden
 | 
						|
  ) {
 | 
						|
    is(
 | 
						|
      promptState.focused,
 | 
						|
      "infoBody",
 | 
						|
      "buttons don't focus on OS X, but infoBody does instead"
 | 
						|
    );
 | 
						|
  } else {
 | 
						|
    is(promptState.focused, expectedState.focused, "Checking focused element");
 | 
						|
  }
 | 
						|
 | 
						|
  if (expectedState.hasOwnProperty("chrome")) {
 | 
						|
    is(
 | 
						|
      promptState.chrome,
 | 
						|
      expectedState.chrome,
 | 
						|
      "Dialog should be opened as chrome"
 | 
						|
    );
 | 
						|
  }
 | 
						|
  if (expectedState.hasOwnProperty("dialog")) {
 | 
						|
    is(
 | 
						|
      promptState.dialog,
 | 
						|
      expectedState.dialog,
 | 
						|
      "Dialog should be opened as a dialog"
 | 
						|
    );
 | 
						|
  }
 | 
						|
  if (expectedState.hasOwnProperty("chromeDependent")) {
 | 
						|
    is(
 | 
						|
      promptState.chromeDependent,
 | 
						|
      expectedState.chromeDependent,
 | 
						|
      "Dialog should be opened as dependent"
 | 
						|
    );
 | 
						|
  }
 | 
						|
  if (expectedState.hasOwnProperty("isWindowModal")) {
 | 
						|
    is(
 | 
						|
      promptState.isWindowModal,
 | 
						|
      expectedState.isWindowModal,
 | 
						|
      "Dialog should be modal"
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
function checkEchoedAuthInfo(expectedState, browsingContext) {
 | 
						|
  return SpecialPowers.spawn(
 | 
						|
    browsingContext,
 | 
						|
    [expectedState.user, expectedState.pass],
 | 
						|
    (expectedUser, expectedPass) => {
 | 
						|
      let doc = this.content.document;
 | 
						|
 | 
						|
      // The server echos back the HTTP auth info it received.
 | 
						|
      let username = doc.getElementById("user").textContent;
 | 
						|
      let password = doc.getElementById("pass").textContent;
 | 
						|
      let authok = doc.getElementById("ok").textContent;
 | 
						|
 | 
						|
      Assert.equal(authok, "PASS", "Checking for successful authentication");
 | 
						|
      Assert.equal(username, expectedUser, "Checking for echoed username");
 | 
						|
      Assert.equal(password, expectedPass, "Checking for echoed password");
 | 
						|
    }
 | 
						|
  );
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Create a Proxy to relay method calls on an nsIAuthPrompt[2] prompter to a chrome script which can
 | 
						|
 * perform the calls in the parent. Out and inout params will be copied back from the parent to
 | 
						|
 * content.
 | 
						|
 *
 | 
						|
 * @param chromeScript The reference to the chrome script that will listen to `proxyPrompter`
 | 
						|
 *                     messages in the parent and call the `methodName` method.
 | 
						|
 *                     The return value from the message handler should be an object with properties:
 | 
						|
 * `rv` - containing the return value of the method call.
 | 
						|
 * `args` - containing the array of arguments passed to the method since out or inout ones could have
 | 
						|
 *          been modified.
 | 
						|
 */
 | 
						|
function PrompterProxy(chromeScript) {
 | 
						|
  return new Proxy(
 | 
						|
    {},
 | 
						|
    {
 | 
						|
      get(target, prop) {
 | 
						|
        return (...args) => {
 | 
						|
          // Array of indices of out/inout params to copy from the parent back to the caller.
 | 
						|
          let outParams = [];
 | 
						|
 | 
						|
          switch (prop) {
 | 
						|
            case "prompt": {
 | 
						|
              outParams = [/* result */ 5];
 | 
						|
              break;
 | 
						|
            }
 | 
						|
            case "promptAuth": {
 | 
						|
              outParams = [];
 | 
						|
              break;
 | 
						|
            }
 | 
						|
            case "promptPassword":
 | 
						|
            case "asyncPromptPassword": {
 | 
						|
              outParams = [/* pwd */ 4];
 | 
						|
              break;
 | 
						|
            }
 | 
						|
            case "promptUsernameAndPassword":
 | 
						|
            case "asyncPromptUsernameAndPassword": {
 | 
						|
              outParams = [/* user */ 4, /* pwd */ 5];
 | 
						|
              break;
 | 
						|
            }
 | 
						|
            default: {
 | 
						|
              throw new Error("Unknown nsIAuthPrompt method");
 | 
						|
            }
 | 
						|
          }
 | 
						|
 | 
						|
          let result;
 | 
						|
          chromeScript
 | 
						|
            .sendQuery("proxyPrompter", {
 | 
						|
              args,
 | 
						|
              methodName: prop,
 | 
						|
            })
 | 
						|
            .then(val => {
 | 
						|
              result = val;
 | 
						|
            });
 | 
						|
          SpecialPowers.Services.tm.spinEventLoopUntil(
 | 
						|
            "Test(prompt_common.js:get)",
 | 
						|
            () => result
 | 
						|
          );
 | 
						|
 | 
						|
          for (let outParam of outParams) {
 | 
						|
            // Copy the out or inout param value over the original
 | 
						|
            args[outParam].value = result.args[outParam].value;
 | 
						|
          }
 | 
						|
 | 
						|
          if (prop == "promptAuth") {
 | 
						|
            args[2].username = result.args[2].username;
 | 
						|
            args[2].password = result.args[2].password;
 | 
						|
            args[2].domain = result.args[2].domain;
 | 
						|
          }
 | 
						|
 | 
						|
          return result.rv;
 | 
						|
        };
 | 
						|
      },
 | 
						|
    }
 | 
						|
  );
 | 
						|
}
 |