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