mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 02:09:05 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			253 lines
		
	
	
	
		
			9.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			253 lines
		
	
	
	
		
			9.6 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/. */
 | 
						|
 | 
						|
import { IOSAppConstants } from "resource://gre/modules/shared/Constants.ios.mjs";
 | 
						|
import Overrides from "resource://gre/modules/Overrides.ios.js";
 | 
						|
 | 
						|
/* eslint mozilla/use-isInstance: 0 */
 | 
						|
HTMLSelectElement.isInstance = element => element instanceof HTMLSelectElement;
 | 
						|
HTMLInputElement.isInstance = element => element instanceof HTMLInputElement;
 | 
						|
HTMLTextAreaElement.isInstance = element =>
 | 
						|
  element instanceof HTMLTextAreaElement;
 | 
						|
HTMLIFrameElement.isInstance = element => element instanceof HTMLIFrameElement;
 | 
						|
HTMLFormElement.isInstance = element => element instanceof HTMLFormElement;
 | 
						|
ShadowRoot.isInstance = element => element instanceof ShadowRoot;
 | 
						|
 | 
						|
HTMLElement.prototype.ownerGlobal = window;
 | 
						|
 | 
						|
// We cannot mock this in WebKit because we lack access to low-level APIs.
 | 
						|
// For completeness, we simply return true when the input type is "password".
 | 
						|
// NOTE: Since now we also include this file for password generator, it might be included multiple times
 | 
						|
// which causes the defineProperty to throw. Allowing it to be overwritten for now is fine, since
 | 
						|
// our code runs in a sandbox and only firefox code can overwrite it.
 | 
						|
Object.defineProperty(HTMLInputElement.prototype, "hasBeenTypePassword", {
 | 
						|
  get() {
 | 
						|
    return this.type === "password";
 | 
						|
  },
 | 
						|
  configurable: true,
 | 
						|
});
 | 
						|
 | 
						|
function setUserInput(value) {
 | 
						|
  this.value = value;
 | 
						|
 | 
						|
  // In React apps, setting .value may not always work reliably.
 | 
						|
  // We dispatch change, input as a workaround.
 | 
						|
  // There are other more "robust" solutions:
 | 
						|
  // - Dispatching keyboard events and comparing the value after setting it
 | 
						|
  //   (https://github.com/fmeum/browserpass-extension/blob/5efb1f9de6078b509904a83847d370c8e92fc097/src/inject.js#L412-L440)
 | 
						|
  // - Using the native setter
 | 
						|
  //   (https://github.com/facebook/react/issues/10135#issuecomment-401496776)
 | 
						|
  // These are a bit more bloated. We can consider using these later if we encounter any further issues.
 | 
						|
  ["input", "change"].forEach(eventName => {
 | 
						|
    this.dispatchEvent(new Event(eventName, { bubbles: true }));
 | 
						|
  });
 | 
						|
 | 
						|
  this.dispatchEvent(new Event("blur", { bubbles: true }));
 | 
						|
}
 | 
						|
 | 
						|
HTMLInputElement.prototype.setUserInput = setUserInput;
 | 
						|
HTMLTextAreaElement.prototype.setUserInput = setUserInput;
 | 
						|
 | 
						|
// Mimic the behavior of .getAutocompleteInfo()
 | 
						|
// It should return an object with a fieldName property matching the autocomplete attribute
 | 
						|
// only if it's a valid value from this list https://searchfox.org/mozilla-central/source/dom/base/AutocompleteFieldList.h#89-149
 | 
						|
// Also found here: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete
 | 
						|
HTMLElement.prototype.getAutocompleteInfo = function () {
 | 
						|
  const autocomplete = this.getAttribute("autocomplete");
 | 
						|
 | 
						|
  return {
 | 
						|
    fieldName: IOSAppConstants.validAutocompleteFields.includes(autocomplete)
 | 
						|
      ? autocomplete
 | 
						|
      : "",
 | 
						|
  };
 | 
						|
};
 | 
						|
 | 
						|
// This function  helps us debug better when an error occurs because a certain mock is missing
 | 
						|
const withNotImplementedError = obj =>
 | 
						|
  new Proxy(obj, {
 | 
						|
    get(target, prop) {
 | 
						|
      if (!Object.keys(target).includes(prop)) {
 | 
						|
        throw new Error(
 | 
						|
          `Not implemented: ${prop} doesn't exist in mocked object `
 | 
						|
        );
 | 
						|
      }
 | 
						|
      return Reflect.get(...arguments);
 | 
						|
    },
 | 
						|
  });
 | 
						|
 | 
						|
// This function will create a proxy for each undefined property
 | 
						|
// This is useful when the accessed property name is unkonwn beforehand
 | 
						|
const undefinedProxy = () =>
 | 
						|
  new Proxy(() => {}, {
 | 
						|
    get() {
 | 
						|
      return undefinedProxy();
 | 
						|
    },
 | 
						|
  });
 | 
						|
 | 
						|
// Webpack needs to be able to statically analyze require statements in order to build the dependency graph
 | 
						|
// In order to require modules dynamically at runtime, we use require.context() to create a dynamic require
 | 
						|
// that is still able to be parsed by Webpack at compile time. The "./" and ".mjs" tells webpack that files
 | 
						|
// in the current directory ending with .mjs might be needed and should be added to the dependency graph.
 | 
						|
// NOTE: This can't handle circular dependencies. A static import can be used in this case.
 | 
						|
// https://webpack.js.org/guides/dependency-management/
 | 
						|
const internalModuleResolvers = {
 | 
						|
  resolveModule(moduleURI) {
 | 
						|
    // eslint-disable-next-line no-undef
 | 
						|
    const moduleResolver = require.context("./", false, /.mjs$/);
 | 
						|
    // Desktop code uses uris for importing modules of the form resource://gre/modules/<module_path>
 | 
						|
    // We only need the filename here
 | 
						|
    const moduleName = moduleURI.split("/").pop();
 | 
						|
    const modulePath =
 | 
						|
      "./" + (Overrides.ModuleOverrides[moduleName] ?? moduleName);
 | 
						|
    return moduleResolver(modulePath);
 | 
						|
  },
 | 
						|
 | 
						|
  resolveModules(obj, modules) {
 | 
						|
    for (const [exportName, moduleURI] of Object.entries(modules)) {
 | 
						|
      const resolvedModule = this.resolveModule(moduleURI);
 | 
						|
      obj[exportName] = resolvedModule?.[exportName];
 | 
						|
    }
 | 
						|
  },
 | 
						|
};
 | 
						|
 | 
						|
// Define mock for XPCOMUtils
 | 
						|
export const XPCOMUtils = withNotImplementedError({
 | 
						|
  defineLazyPreferenceGetter: (
 | 
						|
    obj,
 | 
						|
    prop,
 | 
						|
    pref,
 | 
						|
    defaultValue = null,
 | 
						|
    onUpdate,
 | 
						|
    transform = val => val
 | 
						|
  ) => {
 | 
						|
    const value = IOSAppConstants.prefs[pref] ?? defaultValue;
 | 
						|
    // Explicitly check for undefined since null, false, "" and 0 are valid values
 | 
						|
    if (value === undefined) {
 | 
						|
      throw Error(
 | 
						|
        `Pref ${pref} is not defined and no valid default value was provided.`
 | 
						|
      );
 | 
						|
    }
 | 
						|
    obj[prop] = transform(value);
 | 
						|
  },
 | 
						|
  defineLazyServiceGetter() {
 | 
						|
    // Don't do anything
 | 
						|
    // We need this for OS Auth fixes for formautofill.
 | 
						|
    // TODO(issam, Bug 1894967): Move os auth to separate module and remove this.
 | 
						|
  },
 | 
						|
});
 | 
						|
 | 
						|
// eslint-disable-next-line no-shadow
 | 
						|
export const ChromeUtils = withNotImplementedError({
 | 
						|
  defineLazyGetter: (obj, prop, getFn) => {
 | 
						|
    const callback = prop === "log" ? genericLogger : getFn;
 | 
						|
    obj[prop] = callback?.call(obj);
 | 
						|
  },
 | 
						|
  defineESModuleGetters(obj, modules) {
 | 
						|
    internalModuleResolvers.resolveModules(obj, modules);
 | 
						|
  },
 | 
						|
  importESModule(moduleURI) {
 | 
						|
    return internalModuleResolvers.resolveModule(moduleURI);
 | 
						|
  },
 | 
						|
});
 | 
						|
window.ChromeUtils = ChromeUtils;
 | 
						|
 | 
						|
// Define mock for Region.sys.mjs
 | 
						|
export const Region = withNotImplementedError({
 | 
						|
  home: "US",
 | 
						|
});
 | 
						|
 | 
						|
// Define mock for OSKeyStore.sys.mjs
 | 
						|
export const OSKeyStore = withNotImplementedError({
 | 
						|
  ensureLoggedIn: () => true,
 | 
						|
});
 | 
						|
 | 
						|
// Define mock for Services
 | 
						|
// NOTE: Services is a global so we need to attach it to the window
 | 
						|
// eslint-disable-next-line no-shadow
 | 
						|
export const Services = withNotImplementedError({
 | 
						|
  locale: withNotImplementedError({ isAppLocaleRTL: false }),
 | 
						|
  prefs: withNotImplementedError({ prefIsLocked: () => false }),
 | 
						|
  strings: withNotImplementedError({
 | 
						|
    createBundle: () =>
 | 
						|
      withNotImplementedError({
 | 
						|
        GetStringFromName: () => "",
 | 
						|
        formatStringFromName: () => "",
 | 
						|
      }),
 | 
						|
  }),
 | 
						|
  // TODO(FXCM-936): we should use crypto.randomUUID() instead of Services.uuid.generateUUID() in our codebase
 | 
						|
  // Underneath crypto.randomUUID() uses the same implementation as generateUUID()
 | 
						|
  // https://searchfox.org/mozilla-central/rev/d405168c4d3c0fb900a7354ae17bb34e939af996/dom/base/Crypto.cpp#96
 | 
						|
  // The only limitation is that it's not available in insecure contexts, which should be fine for both iOS and Desktop
 | 
						|
  // since we only autofill in secure contexts
 | 
						|
  uuid: withNotImplementedError({ generateUUID: () => crypto.randomUUID() }),
 | 
						|
});
 | 
						|
window.Services = Services;
 | 
						|
 | 
						|
// Define mock for Localization
 | 
						|
window.Localization = function () {
 | 
						|
  return { formatValueSync: () => "" };
 | 
						|
};
 | 
						|
 | 
						|
// TODO(issam, FXCM-935): In order to create create a universal mock for glean that
 | 
						|
// dispatches telemetry messages to the iOS, we need to modify typedefs in swift. For now, we map the telemetry events
 | 
						|
// to the expected shape. FXCM-935 will tackle cleaning this up.
 | 
						|
window.Glean = {
 | 
						|
  // While moving away from Legacy Telemetry to Glean, the automated script generated the additional categories
 | 
						|
  // `creditcard` and `address`. After bug 1933961 all probes will have moved to category formautofillCreditcards and formautofillAddresses.
 | 
						|
  formautofillCreditcards: undefinedProxy(),
 | 
						|
  formautofill: undefinedProxy(),
 | 
						|
  creditcard: undefinedProxy(),
 | 
						|
  _mapGleanToLegacy: (eventName, { value, ...extra }) => {
 | 
						|
    const eventMapping = {
 | 
						|
      filledModifiedAddressForm: {
 | 
						|
        method: "filled_modified",
 | 
						|
        object: "address_form",
 | 
						|
      },
 | 
						|
      filledAddressForm: { method: "filled", object: "address_form" },
 | 
						|
      detectedAddressForm: { method: "detected", object: "address_form" },
 | 
						|
      filledModifiedAddressFormExt: {
 | 
						|
        method: "filled_modified",
 | 
						|
        object: "address_form_ext",
 | 
						|
      },
 | 
						|
      filledAddressFormExt: { method: "filled", object: "address_form_ext" },
 | 
						|
      detectedAddressFormExt: {
 | 
						|
        method: "detected",
 | 
						|
        object: "address_form_ext",
 | 
						|
      },
 | 
						|
    };
 | 
						|
    // eslint-disable-next-line no-undef
 | 
						|
    webkit.messageHandlers.addressFormTelemetryMessageHandler.postMessage(
 | 
						|
      JSON.stringify({
 | 
						|
        type: "event",
 | 
						|
        category: "address",
 | 
						|
        ...eventMapping[eventName],
 | 
						|
        value,
 | 
						|
        extra,
 | 
						|
      })
 | 
						|
    );
 | 
						|
  },
 | 
						|
  address: new Proxy(
 | 
						|
    {},
 | 
						|
    {
 | 
						|
      get(_target, prop) {
 | 
						|
        return {
 | 
						|
          record: extras => Glean._mapGleanToLegacy(prop, extras),
 | 
						|
        };
 | 
						|
      },
 | 
						|
    }
 | 
						|
  ),
 | 
						|
  // Keeping unused category formautofillAddresses here, because Bug 1933961
 | 
						|
  // will move probes from the glean category address to formautofillAddresses
 | 
						|
  formautofillAddresses: undefinedProxy(),
 | 
						|
};
 | 
						|
 | 
						|
const genericLogger = () =>
 | 
						|
  withNotImplementedError({
 | 
						|
    info: () => {},
 | 
						|
    error: () => {},
 | 
						|
    warn: () => {},
 | 
						|
    debug: () => {},
 | 
						|
  });
 | 
						|
 | 
						|
export { IOSAppConstants as AppConstants } from "resource://gre/modules/shared/Constants.ios.mjs";
 |