How to use fromProtocolColorScheme method in Playwright Internal

Best JavaScript code snippet using playwright-internal

Run Playwright Internal automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

TargetRegistry.js

Source: TargetRegistry.js Github

copy
1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5const {EventEmitter} = ChromeUtils.import('resource://gre/modules/EventEmitter.jsm');
6const {Helper} = ChromeUtils.import('chrome://juggler/content/Helper.js');
7const {SimpleChannel} = ChromeUtils.import('chrome://juggler/content/SimpleChannel.js');
8const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
9const {Preferences} = ChromeUtils.import("resource://gre/modules/Preferences.jsm");
10const {ContextualIdentityService} = ChromeUtils.import("resource://gre/modules/ContextualIdentityService.jsm");
11const {NetUtil} = ChromeUtils.import('resource://gre/modules/NetUtil.jsm');
12const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
13const {OS} = ChromeUtils.import("resource://gre/modules/osfile.jsm");
14
15const helper = new Helper();
16
17const IDENTITY_NAME = 'JUGGLER ';
18const HUNDRED_YEARS = 60 * 60 * 24 * 365 * 100;
19
20const ALL_PERMISSIONS = [
21  'geo',
22  'desktop-notification',
23];
24
25class DownloadInterceptor {
26  constructor(registry) {
27    this._registry = registry
28    this._handlerToUuid = new Map();
29  }
30
31  //
32  // nsIDownloadInterceptor implementation.
33  //
34  interceptDownloadRequest(externalAppHandler, request, browsingContext, outFile) {
35    if (!(request instanceof Ci.nsIChannel))
36      return false;
37    const channel = request.QueryInterface(Ci.nsIChannel);
38    let pageTarget = this._registry._browserBrowsingContextToTarget.get(channel.loadInfo.browsingContext);
39    if (!pageTarget)
40      return false;
41
42    const browserContext = pageTarget.browserContext();
43    const options = browserContext.downloadOptions;
44    if (!options)
45      return false;
46
47    const uuid = helper.generateId();
48    let file = null;
49    if (options.behavior === 'saveToDisk') {
50      file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
51      file.initWithPath(options.downloadsDir);
52      file.append(uuid);
53
54      try {
55        file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
56      } catch (e) {
57        dump(`interceptDownloadRequest failed to create file: ${e}\n`);
58        return false;
59      }
60    }
61    outFile.value = file;
62    this._handlerToUuid.set(externalAppHandler, uuid);
63    const downloadInfo = {
64      uuid,
65      browserContextId: browserContext.browserContextId,
66      pageTargetId: pageTarget.id(),
67      url: request.name,
68      suggestedFileName: externalAppHandler.suggestedFileName,
69    };
70    this._registry.emit(TargetRegistry.Events.DownloadCreated, downloadInfo);
71    return true;
72  }
73
74  onDownloadComplete(externalAppHandler, canceled, errorName) {
75    const uuid = this._handlerToUuid.get(externalAppHandler);
76    if (!uuid)
77      return;
78    this._handlerToUuid.delete(externalAppHandler);
79    const downloadInfo = {
80      uuid,
81    };
82    if (errorName === 'NS_BINDING_ABORTED') {
83      downloadInfo.canceled = true;
84    } else {
85      downloadInfo.error = errorName;
86    }
87    this._registry.emit(TargetRegistry.Events.DownloadFinished, downloadInfo);
88  }
89}
90
91class TargetRegistry {
92  constructor() {
93    EventEmitter.decorate(this);
94
95    this._browserContextIdToBrowserContext = new Map();
96    this._userContextIdToBrowserContext = new Map();
97    this._browserToTarget = new Map();
98    this._browserBrowsingContextToTarget = new Map();
99
100    this._browserProxy = null;
101
102    // Cleanup containers from previous runs (if any)
103    for (const identity of ContextualIdentityService.getPublicIdentities()) {
104      if (identity.name && identity.name.startsWith(IDENTITY_NAME)) {
105        ContextualIdentityService.remove(identity.userContextId);
106        ContextualIdentityService.closeContainerTabs(identity.userContextId);
107      }
108    }
109
110    this._defaultContext = new BrowserContext(this, undefined, undefined);
111
112    Services.obs.addObserver({
113      observe: (subject, topic, data) => {
114        const browser = subject.ownerElement;
115        if (!browser)
116          return;
117        const target = this._browserToTarget.get(browser);
118        if (!target)
119          return;
120        target.emit(PageTarget.Events.Crashed);
121        target.dispose();
122      }
123    }, 'oop-frameloader-crashed');
124
125    Services.mm.addMessageListener('juggler:content-ready', {
126      receiveMessage: message => {
127        const linkedBrowser = message.target;
128        const target = this._browserToTarget.get(linkedBrowser);
129        if (!target)
130          return;
131
132        return {
133          scriptsToEvaluateOnNewDocument: target.browserContext().scriptsToEvaluateOnNewDocument,
134          bindings: target.browserContext().bindings,
135          settings: target.browserContext().settings,
136        };
137      },
138    });
139
140    const onTabOpenListener = (appWindow, window, event) => {
141      const tab = event.target;
142      const userContextId = tab.userContextId;
143      const browserContext = this._userContextIdToBrowserContext.get(userContextId);
144      const hasExplicitSize = appWindow && (appWindow.chromeFlags & Ci.nsIWebBrowserChrome.JUGGLER_WINDOW_EXPLICIT_SIZE) !== 0;
145      const openerContext = tab.linkedBrowser.browsingContext.opener;
146      let openerTarget;
147      if (openerContext) {
148        // Popups usually have opener context.
149        openerTarget = this._browserBrowsingContextToTarget.get(openerContext);
150      } else if (tab.openerTab) {
151        // Noopener popups from the same window have opener tab instead.
152        openerTarget = this._browserToTarget.get(tab.openerTab.linkedBrowser);
153      }
154      if (!browserContext)
155        throw new Error(`Internal error: cannot find context for userContextId=${userContextId}`);
156      const target = new PageTarget(this, window, tab, browserContext, openerTarget);
157      target.updateUserAgent();
158      target.updateTouchOverride();
159      target.updateColorSchemeOverride();
160      if (!hasExplicitSize)
161        target.updateViewportSize();
162      if (browserContext.screencastOptions)
163        target._startVideoRecording(browserContext.screencastOptions);
164    };
165
166    const onTabCloseListener = event => {
167      const tab = event.target;
168      const linkedBrowser = tab.linkedBrowser;
169      const target = this._browserToTarget.get(linkedBrowser);
170      if (target)
171          target.dispose();
172    };
173
174    const domWindowTabListeners = new Map();
175
176    const onOpenWindow = async (appWindow) => {
177
178      let domWindow;
179      if (appWindow instanceof Ci.nsIAppWindow) {
180        domWindow = appWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);
181      } else {
182        domWindow = appWindow;
183        appWindow = null;
184      }
185      if (!(domWindow instanceof Ci.nsIDOMChromeWindow))
186        return;
187      // In persistent mode, window might be opened long ago and might be
188      // already initialized.
189      //
190      // In this case, we want to keep this callback synchronous so that we will call
191      // `onTabOpenListener` synchronously and before the sync IPc message `juggler:content-ready`.
192      if (domWindow.document.readyState === 'uninitialized' || domWindow.document.readyState === 'loading') {
193        // For non-initialized windows, DOMContentLoaded initializes gBrowser
194        // and starts tab loading (see //browser/base/content/browser.js), so we
195        // are guaranteed to call `onTabOpenListener` before the sync IPC message
196        // `juggler:content-ready`.
197        await helper.awaitEvent(domWindow, 'DOMContentLoaded');
198      }
199
200      if (!domWindow.gBrowser)
201        return;
202      const tabContainer = domWindow.gBrowser.tabContainer;
203      domWindowTabListeners.set(domWindow, [
204        helper.addEventListener(tabContainer, 'TabOpen', event => onTabOpenListener(appWindow, domWindow, event)),
205        helper.addEventListener(tabContainer, 'TabClose', onTabCloseListener),
206      ]);
207      for (const tab of domWindow.gBrowser.tabs)
208        onTabOpenListener(appWindow, domWindow, { target: tab });
209    };
210
211    const onCloseWindow = window => {
212      const domWindow = window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);
213      if (!(domWindow instanceof Ci.nsIDOMChromeWindow))
214        return;
215      if (!domWindow.gBrowser)
216        return;
217
218      const listeners = domWindowTabListeners.get(domWindow) || [];
219      domWindowTabListeners.delete(domWindow);
220      helper.removeListeners(listeners);
221      for (const tab of domWindow.gBrowser.tabs)
222        onTabCloseListener({ target: tab });
223    };
224
225    const extHelperAppSvc = Cc["@mozilla.org/uriloader/external-helper-app-service;1"].getService(Ci.nsIExternalHelperAppService);
226    extHelperAppSvc.setDownloadInterceptor(new DownloadInterceptor(this));
227
228    Services.wm.addListener({ onOpenWindow, onCloseWindow });
229    for (const win of Services.wm.getEnumerator(null))
230      onOpenWindow(win);
231  }
232
233  setBrowserProxy(proxy) {
234    this._browserProxy = proxy;
235  }
236
237  getProxyInfo(channel) {
238    const originAttributes = channel.loadInfo && channel.loadInfo.originAttributes;
239    const browserContext = originAttributes ? this.browserContextForUserContextId(originAttributes.userContextId) : null;
240    // Prefer context proxy and fallback to browser-level proxy.
241    const proxyInfo = (browserContext && browserContext._proxy) || this._browserProxy;
242    if (!proxyInfo || proxyInfo.bypass.some(domainSuffix => channel.URI.host.endsWith(domainSuffix)))
243      return null;
244    return proxyInfo;
245  }
246
247  defaultContext() {
248    return this._defaultContext;
249  }
250
251  createBrowserContext(removeOnDetach) {
252    return new BrowserContext(this, helper.generateId(), removeOnDetach);
253  }
254
255  browserContextForId(browserContextId) {
256    return this._browserContextIdToBrowserContext.get(browserContextId);
257  }
258
259  browserContextForUserContextId(userContextId) {
260    return this._userContextIdToBrowserContext.get(userContextId);
261  }
262
263  async newPage({browserContextId}) {
264    const browserContext = this.browserContextForId(browserContextId);
265    const features = "chrome,dialog=no,all";
266    // See _callWithURIToLoad in browser.js for the structure of window.arguments
267    // window.arguments[1]: unused (bug 871161)
268    //                 [2]: referrerInfo (nsIReferrerInfo)
269    //                 [3]: postData (nsIInputStream)
270    //                 [4]: allowThirdPartyFixup (bool)
271    //                 [5]: userContextId (int)
272    //                 [6]: originPrincipal (nsIPrincipal)
273    //                 [7]: originStoragePrincipal (nsIPrincipal)
274    //                 [8]: triggeringPrincipal (nsIPrincipal)
275    //                 [9]: allowInheritPrincipal (bool)
276    //                 [10]: csp (nsIContentSecurityPolicy)
277    //                 [11]: nsOpenWindowInfo
278    const args = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
279    const urlSupports = Cc["@mozilla.org/supports-string;1"].createInstance(
280      Ci.nsISupportsString
281    );
282    urlSupports.data = 'about:blank';
283    args.appendElement(urlSupports); // 0
284    args.appendElement(undefined); // 1
285    args.appendElement(undefined); // 2
286    args.appendElement(undefined); // 3
287    args.appendElement(undefined); // 4
288    const userContextIdSupports = Cc[
289      "@mozilla.org/supports-PRUint32;1"
290    ].createInstance(Ci.nsISupportsPRUint32);
291    userContextIdSupports.data = browserContext.userContextId;
292    args.appendElement(userContextIdSupports); // 5
293    args.appendElement(undefined); // 6
294    args.appendElement(undefined); // 7
295    args.appendElement(Services.scriptSecurityManager.getSystemPrincipal()); // 8
296
297    const window = Services.ww.openWindow(null, AppConstants.BROWSER_CHROME_URL, '_blank', features, args);
298    await waitForWindowReady(window);
299    if (window.gBrowser.browsers.length !== 1)
300      throw new Error(`Unexpected number of tabs in the new window: ${window.gBrowser.browsers.length}`);
301    const browser = window.gBrowser.browsers[0];
302    const target = this._browserToTarget.get(browser);
303    browser.focus();
304    if (browserContext.settings.timezoneId) {
305      if (await target.hasFailedToOverrideTimezone())
306        throw new Error('Failed to override timezone');
307    }
308    return target.id();
309  }
310
311  targets() {
312    return Array.from(this._browserToTarget.values());
313  }
314
315  targetForBrowser(browser) {
316    return this._browserToTarget.get(browser);
317  }
318}
319
320class PageTarget {
321  constructor(registry, win, tab, browserContext, opener) {
322    EventEmitter.decorate(this);
323
324    this._targetId = helper.generateId();
325    this._registry = registry;
326    this._window = win;
327    this._gBrowser = win.gBrowser;
328    this._tab = tab;
329    this._linkedBrowser = tab.linkedBrowser;
330    this._browserContext = browserContext;
331    this._viewportSize = undefined;
332    this._initialDPPX = this._linkedBrowser.browsingContext.overrideDPPX;
333    this._url = 'about:blank';
334    this._openerId = opener ? opener.id() : undefined;
335    this._channel = SimpleChannel.createForMessageManager(`browser::page[${this._targetId}]`, this._linkedBrowser.messageManager);
336    this._screencastInfo = undefined;
337    this._dialogs = new Map();
338
339    const navigationListener = {
340      QueryInterface: ChromeUtils.generateQI([Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference]),
341      onLocationChange: (aWebProgress, aRequest, aLocation) => this._onNavigated(aLocation),
342    };
343    this._eventListeners = [
344      helper.addObserver(this._updateModalDialogs.bind(this), 'tabmodal-dialog-loaded'),
345      helper.addProgressListener(tab.linkedBrowser, navigationListener, Ci.nsIWebProgress.NOTIFY_LOCATION),
346      helper.addEventListener(this._linkedBrowser, 'DOMModalDialogClosed', event => this._updateModalDialogs()),
347    ];
348
349    this._disposed = false;
350    browserContext.pages.add(this);
351    this._registry._browserToTarget.set(this._linkedBrowser, this);
352    this._registry._browserBrowsingContextToTarget.set(this._linkedBrowser.browsingContext, this);
353
354    this._registry.emit(TargetRegistry.Events.TargetCreated, this);
355  }
356
357  dialog(dialogId) {
358    return this._dialogs.get(dialogId);
359  }
360
361  dialogs() {
362    return [...this._dialogs.values()];
363  }
364
365  async windowReady() {
366    await waitForWindowReady(this._window);
367  }
368
369  linkedBrowser() {
370    return this._linkedBrowser;
371  }
372
373  browserContext() {
374    return this._browserContext;
375  }
376
377  updateTouchOverride() {
378    this._linkedBrowser.browsingContext.touchEventsOverride = this._browserContext.touchOverride ? 'enabled' : 'none';
379  }
380
381  updateUserAgent() {
382    this._linkedBrowser.browsingContext.customUserAgent = this._browserContext.defaultUserAgent;
383  }
384
385  _updateModalDialogs() {
386    const prompts = new Set(this._linkedBrowser.tabModalPromptBox ? this._linkedBrowser.tabModalPromptBox.listPrompts() : []);
387    for (const dialog of this._dialogs.values()) {
388      if (!prompts.has(dialog.prompt())) {
389        this._dialogs.delete(dialog.id());
390        this.emit(PageTarget.Events.DialogClosed, dialog);
391      } else {
392        prompts.delete(dialog.prompt());
393      }
394    }
395    for (const prompt of prompts) {
396      const dialog = Dialog.createIfSupported(prompt);
397      if (!dialog)
398        continue;
399      this._dialogs.set(dialog.id(), dialog);
400      this.emit(PageTarget.Events.DialogOpened, dialog);
401    }
402  }
403
404  async updateViewportSize() {
405    // Viewport size is defined by three arguments:
406    // 1. default size. Could be explicit if set as part of `window.open` call, e.g.
407    //   `window.open(url, title, 'width=400,height=400')`
408    // 2. page viewport size
409    // 3. browserContext viewport size
410    //
411    // The "default size" (1) is only respected when the page is opened.
412    // Otherwise, explicitly set page viewport prevales over browser context
413    // default viewport.
414    const viewportSize = this._viewportSize || this._browserContext.defaultViewportSize;
415    const actualSize = await setViewportSizeForBrowser(viewportSize, this._linkedBrowser, this._window);
416    this._linkedBrowser.browsingContext.overrideDPPX = this._browserContext.deviceScaleFactor || this._initialDPPX;
417    await this._channel.connect('').send('awaitViewportDimensions', {
418      width: actualSize.width,
419      height: actualSize.height,
420      deviceSizeIsPageSize: !!this._browserContext.deviceScaleFactor,
421    });
422  }
423
424  setEmulatedMedia(mediumOverride) {
425    this._linkedBrowser.browsingContext.mediumOverride = mediumOverride || '';
426  }
427
428  setColorScheme(colorScheme) {
429    this.colorScheme = fromProtocolColorScheme(colorScheme);
430    this.updateColorSchemeOverride();
431  }
432
433  updateColorSchemeOverride() {
434    this._linkedBrowser.browsingContext.prefersColorSchemeOverride = this.colorScheme || this._browserContext.colorScheme || 'none';
435  }
436
437
438  async setViewportSize(viewportSize) {
439    this._viewportSize = viewportSize;
440    await this.updateViewportSize();
441  }
442
443  close(runBeforeUnload = false) {
444    this._gBrowser.removeTab(this._tab, {
445      skipPermitUnload: !runBeforeUnload,
446    });
447  }
448
449  channel() {
450    return this._channel;
451  }
452
453  id() {
454    return this._targetId;
455  }
456
457  info() {
458    return {
459      targetId: this.id(),
460      type: 'page',
461      browserContextId: this._browserContext.browserContextId,
462      openerId: this._openerId,
463    };
464  }
465
466  _onNavigated(aLocation) {
467    this._url = aLocation.spec;
468    this._browserContext.grantPermissionsToOrigin(this._url);
469  }
470
471  async ensurePermissions() {
472    await this._channel.connect('').send('ensurePermissions', {}).catch(e => void e);
473  }
474
475  async addScriptToEvaluateOnNewDocument(script) {
476    await this._channel.connect('').send('addScriptToEvaluateOnNewDocument', script).catch(e => void e);
477  }
478
479  async addBinding(name, script) {
480    await this._channel.connect('').send('addBinding', { name, script }).catch(e => void e);
481  }
482
483  async applyContextSetting(name, value) {
484    await this._channel.connect('').send('applyContextSetting', { name, value }).catch(e => void e);
485  }
486
487  async hasFailedToOverrideTimezone() {
488    return await this._channel.connect('').send('hasFailedToOverrideTimezone').catch(e => true);
489  }
490
491  async _startVideoRecording({width, height, scale, dir}) {
492    // On Mac the window may not yet be visible when TargetCreated and its
493    // NSWindow.windowNumber may be -1, so we wait until the window is known
494    // to be initialized and visible.
495    await this.windowReady();
496    const file = OS.Path.join(dir, helper.generateId() + '.webm');
497    if (width < 10 || width > 10000 || height < 10 || height > 10000)
498      throw new Error("Invalid size");
499    if (scale && (scale <= 0 || scale > 1))
500      throw new Error("Unsupported scale");
501
502    const screencast = Cc['@mozilla.org/juggler/screencast;1'].getService(Ci.nsIScreencastService);
503    const docShell = this._gBrowser.ownerGlobal.docShell;
504    // Exclude address bar and navigation control from the video.
505    const rect = this.linkedBrowser().getBoundingClientRect();
506    const devicePixelRatio = this._window.devicePixelRatio;
507    const videoSessionId = screencast.startVideoRecording(docShell, file, width, height, scale || 0, devicePixelRatio * rect.top);
508    this._screencastInfo = { videoSessionId, file };
509    this.emit(PageTarget.Events.ScreencastStarted);
510  }
511
512  async _stopVideoRecording() {
513    if (!this._screencastInfo)
514      throw new Error('No video recording in progress');
515    const screencastInfo = this._screencastInfo;
516    this._screencastInfo = undefined;
517    const screencast = Cc['@mozilla.org/juggler/screencast;1'].getService(Ci.nsIScreencastService);
518    const result = new Promise(resolve =>
519      Services.obs.addObserver(function onStopped(subject, topic, data) {
520        if (screencastInfo.videoSessionId != data)
521          return;
522
523        Services.obs.removeObserver(onStopped, 'juggler-screencast-stopped');
524        resolve();
525      }, 'juggler-screencast-stopped')
526    );
527    screencast.stopVideoRecording(screencastInfo.videoSessionId);
528    return result;
529  }
530
531  screencastInfo() {
532    return this._screencastInfo;
533  }
534
535  dispose() {
536    this._disposed = true;
537    if (this._screencastInfo)
538      this._stopVideoRecording().catch(e => dump(`stopVideoRecording failed:\n${e}\n`));
539    this._browserContext.pages.delete(this);
540    this._registry._browserToTarget.delete(this._linkedBrowser);
541    this._registry._browserBrowsingContextToTarget.delete(this._linkedBrowser.browsingContext);
542    try {
543      helper.removeListeners(this._eventListeners);
544    } catch (e) {
545      // In some cases, removing listeners from this._linkedBrowser fails
546      // because it is already half-destroyed.
547      if (e)
548        dump(e.message + '\n' + e.stack + '\n');
549    }
550    this._registry.emit(TargetRegistry.Events.TargetDestroyed, this);
551  }
552}
553
554PageTarget.Events = {
555  ScreencastStarted: Symbol('PageTarget.ScreencastStarted'),
556  Crashed: Symbol('PageTarget.Crashed'),
557  DialogOpened: Symbol('PageTarget.DialogOpened'),
558  DialogClosed: Symbol('PageTarget.DialogClosed'),
559};
560
561function fromProtocolColorScheme(colorScheme) {
562  if (colorScheme === 'light' || colorScheme === 'dark')
563    return colorScheme;
564  if (colorScheme === null || colorScheme === 'no-preference')
565    return undefined;
566  throw new Error('Unknown color scheme: ' + colorScheme);
567}
568
569class BrowserContext {
570  constructor(registry, browserContextId, removeOnDetach) {
571    this._registry = registry;
572    this.browserContextId = browserContextId;
573    // Default context has userContextId === 0, but we pass undefined to many APIs just in case.
574    this.userContextId = 0;
575    if (browserContextId !== undefined) {
576      const identity = ContextualIdentityService.create(IDENTITY_NAME + browserContextId);
577      this.userContextId = identity.userContextId;
578    }
579    this._principals = [];
580    // Maps origins to the permission lists.
581    this._permissions = new Map();
582    this._registry._browserContextIdToBrowserContext.set(this.browserContextId, this);
583    this._registry._userContextIdToBrowserContext.set(this.userContextId, this);
584    this._proxy = null;
585    this.removeOnDetach = removeOnDetach;
586    this.extraHTTPHeaders = undefined;
587    this.httpCredentials = undefined;
588    this.requestInterceptionEnabled = undefined;
589    this.ignoreHTTPSErrors = undefined;
590    this.downloadOptions = undefined;
591    this.defaultViewportSize = undefined;
592    this.deviceScaleFactor = undefined;
593    this.defaultUserAgent = null;
594    this.touchOverride = false;
595    this.colorScheme = 'none';
596    this.screencastOptions = undefined;
597    this.scriptsToEvaluateOnNewDocument = [];
598    this.bindings = [];
599    this.settings = {};
600    this.pages = new Set();
601  }
602
603  setColorScheme(colorScheme) {
604    this.colorScheme = fromProtocolColorScheme(colorScheme);
605    for (const page of this.pages)
606      page.updateColorSchemeOverride();
607  }
608
609  async destroy() {
610    if (this.userContextId !== 0) {
611      ContextualIdentityService.remove(this.userContextId);
612      for (const page of this.pages)
613        page.close();
614      if (this.pages.size) {
615        await new Promise(f => {
616          const listener = helper.on(this._registry, TargetRegistry.Events.TargetDestroyed, () => {
617            if (!this.pages.size) {
618              helper.removeListeners([listener]);
619              f();
620            }
621          });
622        });
623      }
624    }
625    this._registry._browserContextIdToBrowserContext.delete(this.browserContextId);
626    this._registry._userContextIdToBrowserContext.delete(this.userContextId);
627  }
628
629  setProxy(proxy) {
630    // Clear AuthCache.
631    Services.obs.notifyObservers(null, "net:clear-active-logins");
632    this._proxy = proxy;
633  }
634
635  setIgnoreHTTPSErrors(ignoreHTTPSErrors) {
636    if (this.ignoreHTTPSErrors === ignoreHTTPSErrors)
637      return;
638    this.ignoreHTTPSErrors = ignoreHTTPSErrors;
639    const certOverrideService = Cc[
640      "@mozilla.org/security/certoverride;1"
641    ].getService(Ci.nsICertOverrideService);
642    if (ignoreHTTPSErrors) {
643      Preferences.set("network.stricttransportsecurity.preloadlist", false);
644      Preferences.set("security.cert_pinning.enforcement_level", 0);
645      certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(true, this.userContextId);
646    } else {
647      certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(false, this.userContextId);
648    }
649  }
650
651  async setDefaultUserAgent(userAgent) {
652    this.defaultUserAgent = userAgent;
653    for (const page of this.pages)
654      page.updateUserAgent();
655  }
656
657  setTouchOverride(touchOverride) {
658    this.touchOverride = touchOverride;
659    for (const page of this.pages)
660      page.updateTouchOverride();
661  }
662
663  async setDefaultViewport(viewport) {
664    this.defaultViewportSize = viewport ? viewport.viewportSize : undefined;
665    this.deviceScaleFactor = viewport ? viewport.deviceScaleFactor : undefined;
666    await Promise.all(Array.from(this.pages).map(page => page.updateViewportSize()));
667  }
668
669  async addScriptToEvaluateOnNewDocument(script) {
670    this.scriptsToEvaluateOnNewDocument.push(script);
671    await Promise.all(Array.from(this.pages).map(page => page.addScriptToEvaluateOnNewDocument(script)));
672  }
673
674  async addBinding(name, script) {
675    this.bindings.push({ name, script });
676    await Promise.all(Array.from(this.pages).map(page => page.addBinding(name, script)));
677  }
678
679  async applySetting(name, value) {
680    this.settings[name] = value;
681    await Promise.all(Array.from(this.pages).map(page => page.applyContextSetting(name, value)));
682  }
683
684  async grantPermissions(origin, permissions) {
685    this._permissions.set(origin, permissions);
686    const promises = [];
687    for (const page of this.pages) {
688      if (origin === '*' || page._url.startsWith(origin)) {
689        this.grantPermissionsToOrigin(page._url);
690        promises.push(page.ensurePermissions());
691      }
692    }
693    await Promise.all(promises);
694  }
695
696  resetPermissions() {
697    for (const principal of this._principals) {
698      for (const permission of ALL_PERMISSIONS)
699        Services.perms.removeFromPrincipal(principal, permission);
700    }
701    this._principals = [];
702    this._permissions.clear();
703  }
704
705  grantPermissionsToOrigin(url) {
706    let origin = Array.from(this._permissions.keys()).find(key => url.startsWith(key));
707    if (!origin)
708      origin = '*';
709
710    const permissions = this._permissions.get(origin);
711    if (!permissions)
712      return;
713
714    const attrs = { userContextId: this.userContextId || undefined };
715    const principal = Services.scriptSecurityManager.createContentPrincipal(NetUtil.newURI(url), attrs);
716    this._principals.push(principal);
717    for (const permission of ALL_PERMISSIONS) {
718      const action = permissions.includes(permission) ? Ci.nsIPermissionManager.ALLOW_ACTION : Ci.nsIPermissionManager.DENY_ACTION;
719      Services.perms.addFromPrincipal(principal, permission, action, Ci.nsIPermissionManager.EXPIRE_NEVER, 0 /* expireTime */);
720    }
721  }
722
723  setCookies(cookies) {
724    const protocolToSameSite = {
725      [undefined]: Ci.nsICookie.SAMESITE_NONE,
726      'Lax': Ci.nsICookie.SAMESITE_LAX,
727      'Strict': Ci.nsICookie.SAMESITE_STRICT,
728    };
729    for (const cookie of cookies) {
730      const uri = cookie.url ? NetUtil.newURI(cookie.url) : null;
731      let domain = cookie.domain;
732      if (!domain) {
733        if (!uri)
734          throw new Error('At least one of the url and domain needs to be specified');
735        domain = uri.host;
736      }
737      let path = cookie.path;
738      if (!path)
739        path = uri ? dirPath(uri.filePath) : '/';
740      let secure = false;
741      if (cookie.secure !== undefined)
742        secure = cookie.secure;
743      else if (uri && uri.scheme === 'https')
744        secure = true;
745      Services.cookies.add(
746        domain,
747        path,
748        cookie.name,
749        cookie.value,
750        secure,
751        cookie.httpOnly || false,
752        cookie.expires === undefined || cookie.expires === -1 /* isSession */,
753        cookie.expires === undefined ? Date.now() + HUNDRED_YEARS : cookie.expires,
754        { userContextId: this.userContextId || undefined } /* originAttributes */,
755        protocolToSameSite[cookie.sameSite],
756        Ci.nsICookie.SCHEME_UNSET
757      );
758    }
759  }
760
761  clearCookies() {
762    Services.cookies.removeCookiesWithOriginAttributes(JSON.stringify({ userContextId: this.userContextId || undefined }));
763  }
764
765  getCookies() {
766    const result = [];
767    const sameSiteToProtocol = {
768      [Ci.nsICookie.SAMESITE_NONE]: 'None',
769      [Ci.nsICookie.SAMESITE_LAX]: 'Lax',
770      [Ci.nsICookie.SAMESITE_STRICT]: 'Strict',
771    };
772    for (let cookie of Services.cookies.cookies) {
773      if (cookie.originAttributes.userContextId !== this.userContextId)
774        continue;
775      if (cookie.host === 'addons.mozilla.org')
776        continue;
777      result.push({
778        name: cookie.name,
779        value: cookie.value,
780        domain: cookie.host,
781        path: cookie.path,
782        expires: cookie.isSession ? -1 : cookie.expiry,
783        size: cookie.name.length + cookie.value.length,
784        httpOnly: cookie.isHttpOnly,
785        secure: cookie.isSecure,
786        session: cookie.isSession,
787        sameSite: sameSiteToProtocol[cookie.sameSite],
788      });
789    }
790    return result;
791  }
792
793  async setScreencastOptions(options) {
794    this.screencastOptions = options;
795    if (!options)
796      return;
797    const promises = [];
798    for (const page of this.pages)
799      promises.push(page._startVideoRecording(options));
800    await Promise.all(promises);
801  }
802}
803
804class Dialog {
805  static createIfSupported(prompt) {
806    const type = prompt.args.promptType;
807    switch (type) {
808      case 'alert':
809      case 'alertCheck':
810        return new Dialog(prompt, 'alert');
811      case 'prompt':
812        return new Dialog(prompt, 'prompt');
813      case 'confirm':
814      case 'confirmCheck':
815        return new Dialog(prompt, 'confirm');
816      case 'confirmEx':
817        return new Dialog(prompt, 'beforeunload');
818      default:
819        return null;
820    };
821  }
822
823  constructor(prompt, type) {
824    this._id = helper.generateId();
825    this._type = type;
826    this._prompt = prompt;
827  }
828
829  id() {
830    return this._id;
831  }
832
833  message() {
834    return this._prompt.ui.infoBody.textContent;
835  }
836
837  type() {
838    return this._type;
839  }
840
841  prompt() {
842    return this._prompt;
843  }
844
845  dismiss() {
846    if (this._prompt.ui.button1)
847      this._prompt.ui.button1.click();
848    else
849      this._prompt.ui.button0.click();
850  }
851
852  defaultValue() {
853    return this._prompt.ui.loginTextbox.value;
854  }
855
856  accept(promptValue) {
857    if (typeof promptValue === 'string' && this._type === 'prompt')
858      this._prompt.ui.loginTextbox.value = promptValue;
859    this._prompt.ui.button0.click();
860  }
861}
862
863
864function dirPath(path) {
865  return path.substring(0, path.lastIndexOf('/') + 1);
866}
867
868async function waitForWindowReady(window) {
869  if (window.delayedStartupPromise) {
870    await window.delayedStartupPromise;
871  } else {
872    await new Promise((resolve => {
873      Services.obs.addObserver(function observer(aSubject, aTopic) {
874        if (window == aSubject) {
875          Services.obs.removeObserver(observer, aTopic);
876          resolve();
877        }
878      }, "browser-delayed-startup-finished");
879    }));
880  }
881  if (window.document.readyState !== 'complete')
882    await helper.awaitEvent(window, 'load');
883}
884
885async function setViewportSizeForBrowser(viewportSize, browser, window) {
886  await waitForWindowReady(window);
887  if (viewportSize) {
888    const {width, height} = viewportSize;
889    const rect = browser.getBoundingClientRect();
890    window.resizeBy(width - rect.width, height - rect.height);
891    browser.style.setProperty('min-width', width + 'px');
892    browser.style.setProperty('min-height', height + 'px');
893    browser.style.setProperty('max-width', width + 'px');
894    browser.style.setProperty('max-height', height + 'px');
895  } else {
896    browser.style.removeProperty('min-width');
897    browser.style.removeProperty('min-height');
898    browser.style.removeProperty('max-width');
899    browser.style.removeProperty('max-height');
900  }
901  const rect = browser.getBoundingClientRect();
902  return { width: rect.width, height: rect.height };
903}
904
905TargetRegistry.Events = {
906  TargetCreated: Symbol('TargetRegistry.Events.TargetCreated'),
907  TargetDestroyed: Symbol('TargetRegistry.Events.TargetDestroyed'),
908  DownloadCreated: Symbol('TargetRegistry.Events.DownloadCreated'),
909  DownloadFinished: Symbol('TargetRegistry.Events.DownloadFinished'),
910};
911
912var EXPORTED_SYMBOLS = ['TargetRegistry', 'PageTarget'];
913this.TargetRegistry = TargetRegistry;
914this.PageTarget = PageTarget;
915
Full Screen

Accelerate Your Automation Test Cycles With LambdaTest

Leverage LambdaTest’s cloud-based platform to execute your automation tests in parallel and trim down your test execution time significantly. Your first 100 automation testing minutes are on us.

Try LambdaTest

Run JavaScript Tests on LambdaTest Cloud Grid

Execute automation tests with Playwright Internal on a cloud-based Grid of 3000+ real browsers and operating systems for both web and mobile applications.

Test now for Free
LambdaTestX

We use cookies to give you the best experience. Cookies help to provide a more personalized experience and relevant advertising for you, and web analytics for us. Learn More in our Cookies policy, Privacy & Terms of service

Allow Cookie
Sarah

I hope you find the best code examples for your project.

If you want to accelerate automated browser testing, try LambdaTest. Your first 100 automation testing minutes are FREE.

Sarah Elson (Product & Growth Lead)