Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  GeckoViewUtils.sys.mjs   Sprache: unbekannt

 
/* 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 { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { Log } from "resource://gre/modules/Log.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  EventDispatcher: "resource://gre/modules/Messaging.sys.mjs",
  clearTimeout: "resource://gre/modules/Timer.sys.mjs",
  setTimeout: "resource://gre/modules/Timer.sys.mjs",
});

if (AppConstants.platform == "android") {
  ChromeUtils.defineESModuleGetters(lazy, {
    AndroidAppender: "resource://gre/modules/AndroidLog.sys.mjs",
  });
}

export var GeckoViewUtils = {
  /**
   * Define a lazy getter that loads an object from external code, and
   * optionally handles observer and/or message manager notifications for the
   * object, so the object only loads when a notification is received.
   *
   * @param scope     Scope for holding the loaded object.
   * @param name      Name of the object to load.
   * @param service   If specified, load the object from a JS component; the
   *                  component must include the line
   *                  "this.wrappedJSObject = this;" in its constructor.
   * @param module    If specified, load the object from a JS module.
   * @param init      Optional post-load initialization function.
   * @param observers If specified, listen to specified observer notifications.
   * @param ppmm      If specified, listen to specified process messages.
   * @param mm        If specified, listen to specified frame messages.
   * @param ged       If specified, listen to specified global EventDispatcher events.
   * @param once      if true, only listen to the specified
   *                  events/messages/notifications once.
   */
  addLazyGetter(
    scope,
    name,
    { service, module, handler, observers, ppmm, mm, ged, init, once }
  ) {
    ChromeUtils.defineLazyGetter(scope, name, _ => {
      let ret = undefined;
      if (module) {
        ret = ChromeUtils.importESModule(module)[name];
      } else if (service) {
        ret = Cc[service].getService(Ci.nsISupports).wrappedJSObject;
      } else if (typeof handler === "function") {
        ret = {
          handleEvent: handler,
          observe: handler,
          onEvent: handler,
          receiveMessage: handler,
        };
      } else if (handler) {
        ret = handler;
      }
      if (ret && init) {
        init.call(scope, ret);
      }
      return ret;
    });

    if (observers) {
      const observer = (subject, topic, data) => {
        Services.obs.removeObserver(observer, topic);
        if (!once) {
          Services.obs.addObserver(scope[name], topic);
        }
        scope[name].observe(subject, topic, data); // Explicitly notify new observer
      };
      observers.forEach(topic => Services.obs.addObserver(observer, topic));
    }

    if (!this.IS_PARENT_PROCESS) {
      // ppmm, mm, and ged are only available in the parent process.
      return;
    }

    const addMMListener = (target, names) => {
      const listener = msg => {
        target.removeMessageListener(msg.name, listener);
        if (!once) {
          target.addMessageListener(msg.name, scope[name]);
        }
        scope[name].receiveMessage(msg);
      };
      names.forEach(msg => target.addMessageListener(msg, listener));
    };
    if (ppmm) {
      addMMListener(Services.ppmm, ppmm);
    }
    if (mm) {
      addMMListener(Services.mm, mm);
    }

    if (ged) {
      const listener = (event, data, callback) => {
        lazy.EventDispatcher.instance.unregisterListener(listener, event);
        if (!once) {
          lazy.EventDispatcher.instance.registerListener(scope[name], event);
        }
        scope[name].onEvent(event, data, callback);
      };
      lazy.EventDispatcher.instance.registerListener(listener, ged);
    }
  },

  _addLazyListeners(events, handler, scope, name, addFn, handleFn) {
    if (!handler) {
      handler = _ =>
        Array.isArray(name) ? name.map(n => scope[n]) : scope[name];
    }
    const listener = (...args) => {
      let handlers = handler(...args);
      if (!handlers) {
        return;
      }
      if (!Array.isArray(handlers)) {
        handlers = [handlers];
      }
      handleFn(handlers, listener, args);
    };
    if (Array.isArray(events)) {
      addFn(events, listener);
    } else {
      addFn([events], listener);
    }
  },

  /**
   * Add lazy event listeners that only load the actual handler when an event
   * is being handled.
   *
   * @param target  Event target for the event listeners.
   * @param events  Event name as a string or array.
   * @param handler If specified, function that, for a given event, returns the
   *                actual event handler as an object or an array of objects.
   *                If handler is not specified, the actual event handler is
   *                specified using the scope and name pair.
   * @param scope   See handler.
   * @param name    See handler.
   * @param options Options for addEventListener.
   */
  addLazyEventListener(target, events, { handler, scope, name, options }) {
    this._addLazyListeners(
      events,
      handler,
      scope,
      name,
      (events, listener) => {
        events.forEach(event =>
          target.addEventListener(event, listener, options)
        );
      },
      (handlers, listener, args) => {
        if (!options || !options.once) {
          target.removeEventListener(args[0].type, listener, options);
          handlers.forEach(handler =>
            target.addEventListener(args[0].type, handler, options)
          );
        }
        handlers.forEach(handler => handler.handleEvent(args[0]));
      }
    );
  },

  /**
   * Add lazy pref observers, and only load the actual handler once the pref
   * value changes from default, and every time the pref value changes
   * afterwards.
   *
   * @param aPrefs  Prefs as an object or array. Each pref object has fields
   *                "name" and "default", indicating the name and default value
   *                of the pref, respectively.
   * @param handler If specified, function that, for a given pref, returns the
   *                actual event handler as an object or an array of objects.
   *                If handler is not specified, the actual event handler is
   *                specified using the scope and name pair.
   * @param scope   See handler.
   * @param name    See handler.
   * @param once    If true, only observe the specified prefs once.
   */
  addLazyPrefObserver(aPrefs, { handler, scope, name, once }) {
    this._addLazyListeners(
      aPrefs,
      handler,
      scope,
      name,
      (prefs, observer) => {
        prefs.forEach(pref => Services.prefs.addObserver(pref.name, observer));
        prefs.forEach(pref => {
          if (pref.default === undefined) {
            return;
          }
          let value;
          switch (typeof pref.default) {
            case "string":
              value = Services.prefs.getCharPref(pref.name, pref.default);
              break;
            case "number":
              value = Services.prefs.getIntPref(pref.name, pref.default);
              break;
            case "boolean":
              value = Services.prefs.getBoolPref(pref.name, pref.default);
              break;
          }
          if (pref.default !== value) {
            // Notify observer if value already changed from default.
            observer(Services.prefs, "nsPref:changed", pref.name);
          }
        });
      },
      (handlers, observer, args) => {
        if (!once) {
          Services.prefs.removeObserver(args[2], observer);
          handlers.forEach(() => Services.prefs.addObserver(args[2], observer));
        }
        handlers.forEach(handler => handler.observe(...args));
      }
    );
  },

  getRootDocShell(aWin) {
    if (!aWin) {
      return null;
    }
    let docShell;
    try {
      docShell = aWin.QueryInterface(Ci.nsIDocShell);
    } catch (e) {
      docShell = aWin.docShell;
    }
    return docShell.rootTreeItem.QueryInterface(Ci.nsIInterfaceRequestor);
  },

  /**
   * Return the outermost chrome DOM window (the XUL window) for a given DOM
   * window, in the parent process.
   *
   * @param aWin a DOM window.
   */
  getChromeWindow(aWin) {
    const docShell = this.getRootDocShell(aWin);
    return docShell && docShell.domWindow;
  },

  /**
   * Return the content frame message manager (aka the frame script global
   * object) for a given DOM window, in a child process.
   *
   * @param aWin a DOM window.
   */
  getContentFrameMessageManager(aWin) {
    const docShell = this.getRootDocShell(aWin);
    return docShell && docShell.getInterface(Ci.nsIBrowserChild).messageManager;
  },

  /**
   * Return the per-nsWindow EventDispatcher for a given DOM window, in either
   * the parent process or a child process.
   *
   * @param aWin a DOM window.
   */
  getDispatcherForWindow(aWin) {
    try {
      if (!this.IS_PARENT_PROCESS) {
        const mm = this.getContentFrameMessageManager(aWin.top || aWin);
        return mm && lazy.EventDispatcher.forMessageManager(mm);
      }
      const win = this.getChromeWindow(aWin.top || aWin);
      if (!win.closed) {
        return win.WindowEventDispatcher || lazy.EventDispatcher.for(win);
      }
    } catch (e) {}
    return null;
  },

  /**
   * Return promise for waiting for finishing PanZoomState.
   *
   * @param aWindow a DOM window.
   * @return promise
   */
  waitForPanZoomState(aWindow) {
    return new Promise((resolve, reject) => {
      if (
        !aWindow?.windowUtils.asyncPanZoomEnabled ||
        !Services.prefs.getBoolPref("apz.zoom-to-focused-input.enabled")
      ) {
        // No zoomToFocusedInput.
        resolve();
        return;
      }

      let timerId = 0;

      const panZoomState = (aSubject, aTopic, aData) => {
        if (timerId != 0) {
          // aWindow may be dead object now.
          try {
            lazy.clearTimeout(timerId);
          } catch (e) {}
          timerId = 0;
        }

        if (aData === "NOTHING") {
          Services.obs.removeObserver(panZoomState, "PanZoom:StateChange");
          resolve();
        }
      };

      Services.obs.addObserver(panZoomState, "PanZoom:StateChange");

      // "GeckoView:ZoomToInput" has the timeout as 500ms when window isn't
      // resized (it means on-screen-keyboard is already shown).
      // So after up to 500ms, APZ event is sent. So we need to wait for more
      // 500ms.
      timerId = lazy.setTimeout(() => {
        // PanZoom state isn't changed. zoomToFocusedInput will return error.
        Services.obs.removeObserver(panZoomState, "PanZoom:StateChange");
        reject();
      }, 600);
    });
  },

  /**
   * Add logging functions to the specified scope that forward to the given
   * Log.sys.mjs logger. Currently "debug" and "warn" functions are supported. To
   * log something, call the function through a template literal:
   *
   *   function foo(bar, baz) {
   *     debug `hello world`;
   *     debug `foo called with ${bar} as bar`;
   *     warn `this is a warning for ${baz}`;
   *   }
   *
   * An inline format can also be used for logging:
   *
   *   let bar = 42;
   *   do_something(bar); // No log.
   *   do_something(debug.foo = bar); // Output "foo = 42" to the log.
   *
   * @param aTag Name of the Log.sys.mjs logger to forward logs to.
   * @param aScope Scope to add the logging functions to.
   */
  initLogging(aTag, aScope) {
    aScope = aScope || {};
    const tag = "GeckoView." + aTag.replace(/^GeckoView\.?/, "");

    // Only provide two levels for simplicity.
    // For "info", use "debug" instead.
    // For "error", throw an actual JS error instead.
    for (const level of ["DEBUG", "WARN"]) {
      const log = (strings, ...exprs) =>
        this._log(log.logger, level, strings, exprs);

      ChromeUtils.defineLazyGetter(log, "logger", _ => {
        const logger = Log.repository.getLogger(tag);
        logger.parent = this.rootLogger;
        return logger;
      });

      aScope[level.toLowerCase()] = new Proxy(log, {
        set: (obj, prop, value) => obj([prop + " = ", ""], value) || true,
      });
    }
    return aScope;
  },

  get rootLogger() {
    if (!this._rootLogger) {
      this._rootLogger = Log.repository.getLogger("GeckoView");
      // On Android, we'll log to the native android logcat output using
      // __android_log_write. On iOS, fall back to a dump appender.
      if (AppConstants.platform == "android") {
        this._rootLogger.addAppender(new lazy.AndroidAppender());
      } else {
        this._rootLogger.addAppender(new Log.DumpAppender());
      }
      this._rootLogger.manageLevelFromPref("geckoview.logging");
    }
    return this._rootLogger;
  },

  _log(aLogger, aLevel, aStrings, aExprs) {
    if (!Array.isArray(aStrings)) {
      const [, file, line] = new Error().stack.match(/.*\n.*\n.*@(.*):(\d+):/);
      throw Error(
        `Expecting template literal: ${aLevel} \`foo \${bar}\``,
        file,
        +line
      );
    }

    if (aLogger.level > Log.Level.Numbers[aLevel]) {
      // Log disabled.
      return;
    }

    // Do some GeckoView-specific formatting:
    // * Remove newlines so long log lines can be put into multiple lines:
    //   debug `foo=${foo}
    //          bar=${bar}`;
    const strs = Array.from(aStrings);
    const regex = /\n\s*/g;
    for (let i = 0; i < strs.length; i++) {
      strs[i] = strs[i].replace(regex, " ");
    }

    // * Heuristically format flags as hex.
    // * Heuristically format nsresult as string name or hex.
    for (let i = 0; i < aExprs.length; i++) {
      const expr = aExprs[i];
      switch (typeof expr) {
        case "number":
          if (expr > 0 && /\ba?[fF]lags?[\s=:]+$/.test(strs[i])) {
            // Likely a flag; display in hex.
            aExprs[i] = `0x${expr.toString(0x10)}`;
          } else if (expr >= 0 && /\b(a?[sS]tatus|rv)[\s=:]+$/.test(strs[i])) {
            // Likely an nsresult; display in name or hex.
            aExprs[i] = `0x${expr.toString(0x10)}`;
            for (const name in Cr) {
              if (expr === Cr[name]) {
                aExprs[i] = name;
                break;
              }
            }
          }
          break;
      }
    }

    aLogger[aLevel.toLowerCase()](strs, ...aExprs);
  },

  /**
   * Checks whether the principal is supported for permissions.
   *
   * @param {nsIPrincipal} principal
   *        The principal to check.
   *
   * @return {boolean} if the principal is supported.
   */
  isSupportedPermissionsPrincipal(principal) {
    if (!principal) {
      return false;
    }
    if (!(principal instanceof Ci.nsIPrincipal)) {
      throw new Error(
        "Argument passed as principal is not an instance of Ci.nsIPrincipal"
      );
    }
    return this.isSupportedPermissionsScheme(principal.scheme);
  },

  /**
   * Checks whether we support managing permissions for a specific scheme.
   * @param {string} scheme - Scheme to test.
   * @returns {boolean} Whether the scheme is supported.
   */
  isSupportedPermissionsScheme(scheme) {
    return ["http", "https", "moz-extension", "file"].includes(scheme);
  },

  /**
   * Attach nsIOpenWindowInfo when opening GeckoSession
   *
   * @param {string} aSessionId A session id
   * @param {nsIOpenWindowInfo} aOpenWindowInfo Attached nsIOpendWindowInfo
   * @param {string} aName A window name
   * @returns {Promise} resolved when nsIOpenWindowInfo is attached
   */
  waitAndSetupWindow(aSessionId, aOpenWindowInfo, aName) {
    if (!aSessionId) {
      return Promise.reject();
    }

    return new Promise((resolve, reject) => {
      const handler = {
        observe(aSubject, aTopic) {
          if (
            aTopic === "geckoview-window-created" &&
            aSubject.name === aSessionId
          ) {
            // This value will be read by nsFrameLoader while it is being initialized.
            aSubject.browser.openWindowInfo = aOpenWindowInfo;

            // Gecko will use this attribute to set the name of the opened window.
            if (aName) {
              aSubject.browser.setAttribute("name", aName);
            }

            if (
              !aOpenWindowInfo.isRemote &&
              aSubject.browser.hasAttribute("remote")
            ) {
              // We cannot start in remote mode when we have an opener.
              aSubject.browser.setAttribute("remote", "false");
              aSubject.browser.removeAttribute("remoteType");
            }
            Services.obs.removeObserver(handler, "geckoview-window-created");
            if (!aSubject) {
              reject();
              return;
            }
            resolve(aSubject);
          }
        },
      };

      // This event is emitted from createBrowser() in geckoview.js
      Services.obs.addObserver(handler, "geckoview-window-created");
    });
  },
};

ChromeUtils.defineLazyGetter(
  GeckoViewUtils,
  "IS_PARENT_PROCESS",
  _ => Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_DEFAULT
);

[ Dauer der Verarbeitung: 0.31 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge