252 internal MagicHelper(
Browser browser)
254 this.browser = browser;
260 private static readonly Mutex ottoHandshakeGate =
new Mutex(
false,
"GPAL.OttoMagic.PortHandshake");
261 private const int OttoHandshakeGateTimeoutMs = 60_000;
263 List<string> lastErrorMessage =
new List<string>();
264 bool supressedMessage =
false;
265 string lastSessionToken;
266 IBrowser browser {
get;
set; } =
null;
269 private string _clientApiBase;
289 if (
false ==
string.IsNullOrEmpty(url) && url != _clientApiBase)
291 _client.WithAPIBase(url);
292 _clientApiBase = url;
306 Client.Back().Execute();
309 public string CaptureVisibleTab()
311 return Client.CaptureVisibleTab().Execute();
325 public string CheckNetworkIdle(
int? maxConnections =
null,
int? timeoutMs =
null,
int? pruneMs =
null)
331 string retVal = Client
332 .WithEndpoint(ApiEndpoint.CheckNetworkIdle)
333 .WithMaxConnections(maxConnections ?? settings?.NetworkIdleMaxConnections ?? 0)
334 .WithTimeoutMs(timeoutMs ?? settings?.NetworkIdleTimeoutMs ?? 30_000)
335 .WithPruneMs(pruneMs ?? settings?.NetworkIdlePruneMs ?? 3_000)
349 if (
null == URLorTabId)
350 return Client.CloseTab().Execute<
TabTuple>();
351 else if (URLorTabId is
string v)
352 return Client.CloseTab(v).Execute<
TabTuple>();
353 else if (URLorTabId is
GPALUrl u)
354 return Client.CloseTab(u).Execute<
TabTuple>();
355 else if (URLorTabId is
int i)
356 return Client.CloseTab(URLorTabId).Execute<
TabTuple>();
358 return Client.CloseTab().Execute<
TabTuple>();
372 public bool DeleteStorage(WebsiteStorageType storageType,
bool deleteAcrossOrigins =
false,
string domain =
null,
string path =
null,
string key =
null,
string storeName =
null)
376 fluentObject = Client.DeleteStorage(storageType);
378 if (
false ==
string.IsNullOrEmpty(domain))
379 fluentObject.WithStorageDomain(domain);
381 if (
false ==
string.IsNullOrEmpty(path))
382 fluentObject.WithStoragePath(path);
384 if (
false ==
string.IsNullOrEmpty(key))
385 fluentObject.WithStorageKey(key);
387 if (
false ==
string.IsNullOrEmpty(storeName))
388 fluentObject.WithStorageStoreName(storeName);
393 string deleteResult = fluentObject.Execute();
395 return null != deleteResult;
406 public string GetStorage(WebsiteStorageType storageType,
string domain =
null,
string path =
null,
string key =
null,
string storeName =
null)
410 fluentObject = Client.GetStorage(storageType);
412 if (
false ==
string.IsNullOrEmpty(domain))
413 fluentObject.WithStorageDomain(domain);
415 if (
false ==
string.IsNullOrEmpty(path))
416 fluentObject.WithStoragePath(path);
418 if (
false ==
string.IsNullOrEmpty(key))
419 fluentObject.WithStorageKey(key);
421 if (
false ==
string.IsNullOrEmpty(storeName))
422 fluentObject.WithStorageStoreName(storeName);
424 return fluentObject.Execute();
427 public bool SetStorage(WebsiteStorageType storageType,
string data,
string domain =
null,
string path =
null,
string key =
null,
string storeName =
null)
432 fluentObject = Client.SetStorage(storageType);
434 if (
false ==
string.IsNullOrEmpty(domain))
435 fluentObject.WithStorageDomain(domain);
437 if (
false ==
string.IsNullOrEmpty(path))
438 fluentObject.WithStoragePath(path);
440 if (
false ==
string.IsNullOrEmpty(key))
441 fluentObject.WithStorageKey(key);
443 if (
false ==
string.IsNullOrEmpty(storeName))
444 fluentObject.WithStorageStoreName(storeName);
446 if (
false ==
string.IsNullOrEmpty(data))
447 fluentObject.WithData(data);
449 return fluentObject.Execute<
bool>();
458 Client.Forward().Execute();
467 Client.FullScreen().Execute();
478 bool areRobotsAllowed =
true;
480 URL.
ForUrl(GetFullUrl(URL?.Url, browser, out areRobotsAllowed));
483 ((
Browser)browser)._areRobotsAllowed = areRobotsAllowed;
485 var result = Client.GoTo(URL?.Url).Execute();
486 browser.ServerResponseCode = ((
RESTClient)Client).StatusCode;
499 string retVal = Client.GetContentAndCss(elementId).Execute();
504 if (
true == retVal?.StartsWith(
"\"") &&
true == retVal.EndsWith(
"\"") && 1 < retVal.Length)
505 retVal = Newtonsoft.Json.JsonConvert.DeserializeObject<
string>(retVal);
509 public Dictionary<string, object> GetCssAttributes(
string elementId)
511 return Client.GetCssAttributes(elementId).Execute<Dictionary<string, object>>();
513 public Dictionary<string, object> GetDomAttributes(
string elementId)
515 return Client.GetDomAttributes(elementId).Execute<Dictionary<string, object>>();
517 public Dictionary<string, object> GetDomProperties(
string elementId)
519 return Client.GetDomProperties(elementId).Execute< Dictionary<string, object>>();
529 return Client.GetCurrentUrl().Execute();
551 string retVal =
null;
553 if (lastSessionToken != sessionToken)
555 lastErrorMessage.Clear();
556 supressedMessage =
false;
557 lastSessionToken = sessionToken;
560 dynamic obj = Client.GetReadyStatus().Execute();
561 if (
"null" != obj &&
"NOTOK" != obj)
562 retVal = obj ??
@"""loading""";
566 string currentErrorMessage = $
"Document.Ready status [{retVal}]";
568 if (
false == lastErrorMessage.Contains(currentErrorMessage))
570 lastErrorMessage.Add(currentErrorMessage);
572 supressedMessage =
false;
574 else if (
false == supressedMessage)
576 GPAL.PublishSimpleEvent(GPALEventType.INFO,
"Supressing repeat messages", currentErrorMessage, GPALObjectType.Other);
577 supressedMessage =
true;
603 URL =
new GPALUrl(
"google.com");
605 bool areRobotsAllowed =
true;
607 URL.
ForUrl(GetFullUrl(URL.
Url, browser, out areRobotsAllowed));
610 ((
Browser)browser)._areRobotsAllowed = (
bool) areRobotsAllowed;
613 browser.ServerResponseCode = ((
RESTClient)Client).StatusCode;
629 browser.ServerResponseCode = ((
RESTClient)Client).StatusCode;
632 else if (URLorTabId is
TabTuple tuple)
635 browser.ServerResponseCode = ((
RESTClient)Client).StatusCode;
640 GPAL.
PublishSimpleEvent(GPALEventType.ERROR, $
"URLorTabId is unknown type [{URLorTabId.GetType()}]",
this, GPALObjectType.Other);
652 Client.SwitchToElement(elementId).Execute();
662 Client.SwitchToElement(elementId).Execute();
671 Client.Minimize().Execute();
680 Client.Maximize().Execute();
690 Client.ScrollIntoView(elementId).Execute();
691 Client.Hover(elementId).Execute();
700 Client.Normal().Execute();
714 URL.ForUrl(GetFullUrl(URL.Url, browser, out ((
Browser)browser)._areRobotsAllowed));
717 browser.ServerResponseCode = ((
RESTClient)Client).StatusCode;
729 return Client.NextTab().Execute<
TabTuple>();
740 return Client.TabCount().Execute<
int>();
760 Client.OverrideReferrer(referrer).Execute();
770 Client.SetUserAgent(userAgent).Execute();
779 Client.PageDown().Execute();
788 Client.PageEnd().Execute();
797 Client.PageTop().Execute();
806 Client.PageUp().Execute();
814 Client.PressModifierKey(modifierKeys).Execute();
823 return Client.PreviousTab().Execute<
TabTuple>();
834 return Client.PreviousWindow().Execute<
WindowTuple>();
842 Client.Refresh().Execute();
851 Client.ReleaseModifierKey(modifierKeys).Execute();
860 Client.Restore().Execute();
870 Client.ScrollWindowByHorizontal(pixels).Execute();
880 Client.ScrollWindowByVertical(pixels).Execute();
889 Client.SetGoogleReferrer().Execute();
903 Client.SetDownloadFilename(Path.GetFileName(downloadPath)).Execute();
913 return Client.WindowScreenLeft().Execute<
int>();
923 return Client.WindowScreenTop().Execute<
int>();
933 Client.Focus(elementId).Execute();
943 Client.FireChangeEvent(elementId).Execute();
952 Rectangle rect =
new Rectangle();
953 var result = Client.GetWindowRectangle().Execute<Dictionary<string, object>>();
954 Dictionary<string, object> obj =
null;
958 obj = (Dictionary<string, object>)result;
961 var left = obj.ContainsKey(
"X") ? Convert.ToInt32(obj[
"X"]) : 0;
962 var top = obj.ContainsKey(
"Y") ? Convert.ToInt32(obj[
"Y"]) : 0;
963 var width = obj.ContainsKey(
"Width") ? Convert.ToInt32(obj[
"Width"]) : 0;
964 var height = obj.ContainsKey(
"Height") ? Convert.ToInt32(obj[
"Height"]) : 0;
966 rect =
new Rectangle(left, top, width, height);
978 Client.ScrollIntoView(elementId).Execute();
989 Client.ScrollElement(elementId).WithHPixels(hPixels).WithVPixels(vPixels).Execute();
999 return Client.GetElementAttributeHash(elementId).Execute();
1009 List<GPALElement> tmpElements = Client.GetOptions(elementId).Execute<List<GPALElement>>();
1019 return Client.GetPageSource().Execute();
1029 return Client.GetParentNode(elementId).Execute<List<GPALElement>>()[0];
1041 return Client.GetAttribute(elementId).WithAttribute(attribute).Execute();
1053 Client.SetAttribute(elementId).WithAttribute(attribute).WithValue(value).Execute();
1056 public void SetRange(
string elementId,
int rangeValue)
1058 Client.SetRange(elementId).WithRangeValue(rangeValue).Execute();
1069 Client.SetValueFrom(destElementId).WithElementId(srcSelector).Execute();
1079 Client.LeftClick(elementId).Execute();
1090 Client.LeftDoubleClick(elementId).Execute();
1100 Client.MiddleClick(elementId).Execute();
1111 Client.RightClick(elementId).Execute();
1114 public void SelectClick(
string elementId, dynamic indexOrValue)
1116 if (indexOrValue is
int idx)
1117 Client.SelectClick(elementId).WithSelectIndex(idx).Execute();
1119 Client.SelectClick(elementId).WithSelectValue(indexOrValue).Execute();
1130 return Client.IsVisibleInViewport(elementId).Execute<
bool>();
1140 return Client.IsEndOfPage().Execute<
bool>();
1150 Client.Hover(elementId).Execute();
1162 public void DragAndDrop(
string elementId,
int deltaX,
int deltaY,
int offsetX = 0,
int offsetY = 0)
1164 Client.DragAndDrop(elementId).WithDeltaX(deltaX).WithDeltaY(deltaY).WithOffsetX(offsetX).WithOffsetY(offsetY).Execute();
1174 return Client.WindowInnerHeight().Execute<
int>();
1184 return Client.WindowInnerWidth().Execute<
int>();
1194 return Client.WindowOuterHeight().Execute<
int>();
1204 return Client.WindowOuterWidth().Execute<
int>();
1219 public string Fetch(
string url,
string method =
null,
string body =
null,
string contentType =
null,
string[] headers =
null,
bool asBytes =
false)
1221 string retVal = Client
1225 .WithContentType(contentType)
1226 .WithHeaders(headers)
1232 return "NOTOK" == retVal ||
"null" == retVal ? null : retVal;
1242 return Client.WindowPageOffsetX().Execute<
int>();
1252 return Client.WindowPageOffsetY().Execute<
int>();
1270 return true == Client.ElementFromPoint(elementId, x, y).Execute<
bool>();
1283 return true == Client.TrustedLeftClick(x, y, holdMs).Execute<
bool>();
1296 return true == Client.TrustedMiddleClick(x, y, holdMs).Execute<
bool>();
1299 public Rectangle GetBoundingClientRect(
string elementId)
1301 Rectangle rect =
new Rectangle(0,0,0,0);
1302 var result = Client.GetBoundingClientRect(elementId).Execute<Dictionary<string, object>>();
1303 Dictionary<string, object> obj =
null;
1307 obj = (Dictionary<string, object>)result;
1310 var left = obj.ContainsKey(
"left") ? Convert.ToInt32(obj[
"left"]) : 0;
1311 var top = obj.ContainsKey(
"top") ? Convert.ToInt32(obj[
"top"]) : 0;
1312 var width = obj.ContainsKey(
"width") ? Convert.ToInt32(obj[
"width"]) : 0;
1313 var height = obj.ContainsKey(
"height") ? Convert.ToInt32(obj[
"height"]) : 0;
1315 rect =
new Rectangle(left, top, width, height);
1328 Client.HideElement(elementId).Execute();
1338 string answered = Client.InjectScript(script).Execute();
1343 if (
true == answered?.Contains(
"error"))
1344 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"The extension did not register the script [{answered}]",
this, GPALObjectType.Other);
1357 return Client.ExecuteJavaScript(script).Execute();
1369 string retVal = Client.GetDocumentStamp().Execute();
1374 if (
true == retVal?.StartsWith(
"\"") &&
true == retVal.EndsWith(
"\"") && 1 < retVal.Length)
1375 retVal = Newtonsoft.Json.JsonConvert.DeserializeObject<
string>(retVal);
1389 return Client.GetHydrationScripts().Execute();
1398 Client.ClearInjectedScripts().Execute();
1411 return Client.IsClickable(elementId).Execute<
bool>();
1423 return Client.IsDisplayed(elementId).Execute<
bool>();
1435 return Client.IsEnabled(elementId).Execute<
bool>();
1443 Client.ClearReferrer().Execute();
1454 return Client.Evaluate(xpath).Execute<
GPALElement>();
1465 return Client.EvaluateAll(xpath).Execute<List<GPALElement>>();
1475 return Client.EvaluatePersistent(xpath).Execute<
GPALElement>();
1486 return Client.EvaluateAllPersistent(xpath).Execute<List<GPALElement>>();
1496 return Client.GetUserAgent().Execute();
1506 return Client.GetLanguages().Execute();
1518 public void CaptureCalls(
bool capture =
true,
string urlFragment =
null,
bool clear =
false)
1522 if (
false ==
string.IsNullOrWhiteSpace(urlFragment)) capturing.WithCallFilter(urlFragment);
1523 if (
true == clear) capturing.WithClearCalls(
true);
1525 capturing.Execute();
1535 return Client.GetCapturedCalls().Execute();
1546 Client.FillInAppend(elementId).WithText(text).Execute();
1557 Client.FillInInsert(elementId).WithText(text).Execute();
1568 Client.FillInOverwrite(elementId).WithText(text).Execute();
1579 Client.LeftClickAndDownload(elementId).WithDownloadFile(Path.GetFileName(filenameAndPath)).Execute();
1589 Client.LeftClickAndUpload(elementId).WithUploadFile(filenameAndPath).Execute();
1599 Client.LeftClickAndUpload(elementId).WithUploadFiles(filenamesAndPaths).Execute();
1611 var elem = Client.QuerySelector(css).Execute<
GPALElement>();
1623 return Client.QuerySelectors(css).Execute<List<GPALElement>>();
1634 var persist = Client.QueryPersistentSelector(css).Execute<
GPALElement>();
1647 return Client.QueryPersistentSelectors(css).Execute<List<GPALElement>>();
1658 Client.ScrollWindow(hPixels, vPixels).Execute();
1668 Client.SendKey(vkcode).Execute();
1679 Client.SendString(text, delayMs).Execute();
1689 Client.SwitchToDefaultContent().Execute();
1699 Client.SwitchToShadowRoot(elementId).Execute();
1702 #region Window Handling
1708 URL.ForUrl(GetFullUrl(URL.Url, browser, out ((
Browser)browser)._areRobotsAllowed));
1711 browser.ServerResponseCode = ((
RESTClient)Client).StatusCode;
1716 public WindowTuple GoToWindow(dynamic urlOrId)
1718 if (urlOrId is
string v)
1719 return Client.GoToWindow(v).Execute<WindowTuple>();
1721 return Client.GoToWindow(urlOrId.ToString()).Execute<WindowTuple>();
1730 return Client.CloseBrowser().Execute();
1735 if (urlOrId is
string v)
1736 return Client.CloseWindow(v).Execute<
WindowTuple>();
1738 return Client.CloseWindow(urlOrId.ToString()).Execute<
WindowTuple>();
1741 public WindowTuple GetCurrentWindow()
1743 return Client.GetCurrentWindow().Execute<WindowTuple>();
1745 #endregion Window Handling
1754 return Client.GetBrowserSettings().Execute();
1764 return Client.GetGpalSettings().Execute();
1774 return Client.GetSettings().Execute();
1784 return Client.GetWorkflow().Execute();
1786 #endregion GPAL Studio
1804 static void PublishBrowserOutput(
BrowserSettings browserSettings, DataReceivedEventArgs data)
1806 if (
null != data.Data)
1807 GPAL.
PublishSimpleEvent(GPALEventType.DEEPDEBUG, $
"[{browserSettings.BrowserType}] {data.Data}", browserSettings, GPALObjectType.Other);
1818 private static void AdoptFirefoxChild(BrowserSettings browserSettings,
int launcherPid, DateTime deadline)
1820 if (1 > launcherPid)
return;
1822 while (DateTime.UtcNow < deadline)
1828 using (var searcher =
new ManagementObjectSearcher(
1829 $
"SELECT ProcessId FROM Win32_Process WHERE ParentProcessId = {launcherPid} AND Name = 'firefox.exe'"))
1831 foreach (ManagementObject found
in searcher.Get())
1833 childPid = Convert.ToInt32(found[
"ProcessId"]);
1848 browserSettings.Process = System.Diagnostics.Process.GetProcessById(childPid);
1850 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"FireFox launcher [{launcherPid}] handed over to browser process [{childPid}]", browserSettings, GPALObjectType.Browser);
1864 public Process LaunchBrowser(Browser browser, GPALUrl URL, List<string> additionalArguments =
null)
1866 BrowserSettings browserSettings = browser.BrowserSettings;
1867 bool created =
false;
1868 int secondsToWait = 30;
1869 TimeSpan timeout = TimeSpan.FromSeconds(secondsToWait);
1871 if (
null == browserSettings)
1874 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
"BrowserSettings parameter is null. Using chrome puppeteer port.",
null, GPALObjectType.Other);
1875 browser = (Browser)GPAL.Browser.ToGPALObject();
1876 browserSettings = ((Browser)browser).BrowserSettings;
1879 if (
null != browserSettings.ExistingBrowserPort)
1885 foreach (Browser b
in GPAL.Browsers)
1887 && (b.BrowserSettings.DebugPort == browserSettings.ExistingBrowserPort
1888 || b.BrowserSettings.ExistingBrowserPort == browserSettings.ExistingBrowserPort))
1890 browser.PuppeteerClient = b.PuppeteerClient;
1891 browser.PuppeteerCommunicator = b.PuppeteerCommunicator;
1899 ChromeProfileManager.SweepAbandonedTempProfiles();
1901 if (
null == URL ||
null == URL?.Url)
1902 URL = BrowserType.FireFox != browserSettings.BrowserType ?
"about:blank" :
"google.com";
1904 URL.ForUrl(GetFullUrl(URL?.Url, browser, out ((Browser)browser)._areRobotsAllowed,
true));
1906 if (
false == ((Browser)browser).AreRobotsAllowed &&
true == ((Browser)browser).BrowserSettings.ObeyRobotsTxt)
1908 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"Visiting [{URL?.Url}] is disallowed by robots.txt and your request to honor it. Not going to URL. Workflow will fail.", browser, GPALObjectType.Browser);
1909 URL.ForUrl(
"https://google.com");
1912 bool headless = browserSettings.UseHeadless;
1913 var chromeArguments = additionalArguments ??
new List<string>();
1917 switch (browserSettings.BrowserType)
1919 case BrowserType.Chrome:
1920 browserPath = Puppeteer.GetChromePath();
1922 case BrowserType.Edge:
1923 browserPath = Puppeteer.GetEdgePath();
1925 case BrowserType.FireFox:
1926 browserPath = Puppeteer.GetFirefoxPath();
1929 Exception ex =
new Exception(
"Unsupported Browser Type");
1930 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Unsupported browser type [{browserSettings.BrowserType}]", browserSettings, GPALObjectType.Other, ex);
1934 if (
string.IsNullOrEmpty(browserPath) ||
false == File.Exists(browserPath))
1936 string msg = $
"[{browserSettings.BrowserType}] executable not found at [{browserPath}]";
1937 GPAL.PublishSimpleEvent(GPALEventType.ERROR, msg, browserSettings, GPALObjectType.Other);
1938 throw new NotFoundException(msg);
1942 if (browserSettings.BrowserType == BrowserType.Chrome || browserSettings.BrowserType == BrowserType.Edge)
1944 string userAgent =
null;
1947 if (
false ==
string.IsNullOrEmpty(browserSettings.ProfileUserName) ||
false ==
string.IsNullOrEmpty(browserSettings.ProfileName))
1949 browserSettings.ProfileDataDirectory = BrowserHelper.GetBrowserProfileDirectory(browserSettings);
1950 if (
false ==
string.IsNullOrEmpty(browserSettings.ProfileName))
1956 chromeArguments.Add(
$@"--profile-directory=""{BrowserHelper.FindProfileDirectory(browserSettings.ProfileDataDirectory, browserSettings.ProfileName)}""");
1959 else if (
true ==
string.IsNullOrEmpty(browserSettings.ProfileDataDirectory))
1961 browserSettings.ProfileDataDirectory = ChromeProfileManager.CreateTempUserProfile(
1962 browserSettings.DownloadLocation ?? FileHelper.GetDefaultDownloadDirectory(browser),
1963 browserSettings.PromptForDownload,
1964 browserSettings.OpenPDFExternally,
1965 browserSettings.LoadImages,
1966 browserSettings.UseOttoMagic
1968 browserSettings.TempProfileCreated =
true;
1976 if (
false == browserSettings.TempProfileCreated &&
false ==
string.IsNullOrEmpty(browserSettings.ProfileDataDirectory))
1977 browserSettings.PreviousDownloadPreferences = ChromeProfileManager.ApplyDownloadPreferences(
1978 browserSettings.ProfileDataDirectory,
1979 browserSettings.DownloadLocation,
1980 browserSettings.PromptForDownload,
1981 browserSettings.OpenPDFExternally);
1987 if (
false == browserSettings.TempProfileCreated)
1988 BrowserHelper.RefuseIfProfileIsOpen(browserSettings);
1991 if (
true == browserSettings.UsePuppeteer)
1993 if (
true == browserSettings.DebugPipe)
1994 chromeArguments.Add(
"--remote-debugging-pipe --enable-unsafe-extension-debugging --remote-debugging-port=0");
1999 if (
null == browserSettings.DebugPort)
2000 browserSettings.DebugPort = BrowserHelper.FindFreePort();
2002 browserSettings.PuppeteerUrl = $
"http://localhost:{browserSettings.DebugPort}";
2003 chromeArguments.Add($
"--remote-debugging-port={browserSettings.DebugPort.Value}");
2016 if (
false == browserSettings.FullScreen &&
2017 false == browserSettings.Maximize &&
2018 false == browserSettings.Minimize)
2020 if (
false == browserSettings.WindowSize.IsEmpty)
2024 chromeArguments.Add($
"--window-size={browserSettings.WindowSize.Width},{browserSettings.WindowSize.Height}");
2028 if (0 != browserSettings.WindowSize.X || 0 != browserSettings.WindowSize.Y)
2029 chromeArguments.Add($
"--window-position={browserSettings.WindowSize.X},{browserSettings.WindowSize.Y}");
2031 else if (browserSettings.UseHeadless)
2032 chromeArguments.Add(
"--start-fullscreen");
2034 else if (
true == browserSettings.FullScreen)
2035 chromeArguments.Add(
"--start-fullscreen");
2036 else if (
true == browserSettings.Maximize)
2037 chromeArguments.Add(
"--start-maximized");
2043 chromeArguments.Add(
"--excludeSwitches=enable-automation");
2044 chromeArguments.Add(
"--useAutomationExtension=false");
2048 chromeArguments.Add(
"--no-first-run");
2049 chromeArguments.Add(
"--no-default-browser-check");
2059 chromeArguments.Add(
"--disable-dev-shm-usage");
2064 if (
true == browserSettings.TempProfileCreated)
2066 chromeArguments.Add(
"--disable-sync");
2067 chromeArguments.Add(
"--disable-signin-promo");
2071 chromeArguments.Add(
"--disable-infobars");
2072 chromeArguments.Add(
"--disable-session-crashed-bubble");
2073 chromeArguments.Add(
"--hide-crash-restore-bubble");
2074 chromeArguments.Add(
"--noerrdialogs");
2078 string disableFeatures =
"TranslateUI,Translate";
2080 if (BrowserType.Edge == browserSettings.BrowserType)
2082 disableFeatures +=
",msShoppingTrigger,msShopping,msEdgeShoppingUI,msEdgeShoppingList";
2085 if (
true == browserSettings.TempProfileCreated)
2086 disableFeatures +=
",msImplicitSignin,msEdgeImplicitSignin,msIdentityImplicitSignin";
2089 chromeArguments.Add($
"--disable-features={disableFeatures}");
2090 chromeArguments.Add(
"--load-media-router-component-extension");
2091 chromeArguments.Add(
"--enable-media-router");
2092 chromeArguments.Add(
"--force-discovery");
2093 chromeArguments.Add(
"--allow-local-network-access");
2095 if (
false ==
string.IsNullOrEmpty(browserSettings.ProfileDataDirectory))
2096 chromeArguments.Add(
$@"--user-data-dir=""{browserSettings.ProfileDataDirectory.Replace("\\
", "/
")}""");
2101 chromeArguments.Add($
"--blink-settings=imagesEnabled={browserSettings.LoadImages.ToString().ToLower()}");
2106 chromeArguments.Add(
"--headless=new");
2107 chromeArguments.Add(
"--disable-notifications");
2108 chromeArguments.Add(
"--disable-gpu");
2109 if (AutomationEngine.PuppeteerPipe == browserSettings.AutomationEngine || AutomationEngine.PuppeteerPipeHW == browserSettings.AutomationEngine)
2110 chromeArguments.Add(
"--no-sandbox");
2113 userAgent =
GetUserAgent(browserSettings, chromeArguments);
2115 userAgent = userAgent?.Trim(
'"');
2117 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Headless [{browserSettings.BrowserType}] using UserAgent [{userAgent}]", browserSettings, GPALObjectType.Other);
2119 chromeArguments.Add($
"--user-agent=\"{userAgent}\"");
2123 if (
false == browserSettings.BlockPopUps)
2124 chromeArguments.Add(
"--disable-popup-blocking");
2128 if (browserSettings.StealthType.HasFlag(StealthType.DarkMode))
2129 chromeArguments.Add(
"--force-dark-mode");
2139 else if (browserSettings.BrowserType == BrowserType.FireFox)
2142 var firefoxArguments =
new List<string>();
2146 firefoxArguments.Add(
"-headless");
2147 firefoxArguments.Add($
"-width={Puppeteer.GetScreenWidth()}");
2148 firefoxArguments.Add($
"-height={Puppeteer.GetScreenHeight()}");
2149 firefoxArguments.Add(
"--disable-notifications");
2159 if (
true ==
string.IsNullOrEmpty(browserSettings.ProfileDataDirectory))
2161 browserSettings.ProfileDataDirectory = FirefoxProfileManager.CreateTempProfileDirectory();
2165 browserSettings.TempProfileCreated =
true;
2170 if (
false == browserSettings.TempProfileCreated)
2171 BrowserHelper.RefuseIfProfileIsOpen(browserSettings);
2176 if (
false == browserSettings.TempProfileCreated)
2177 browserSettings.PreviousFirefoxPreferences = FirefoxProfileManager.CreateUserJs(browserSettings.ProfileDataDirectory, browserSettings);
2179 FirefoxProfileManager.CreateUserJs(browserSettings.ProfileDataDirectory, browserSettings);
2181 if (
false ==
string.IsNullOrEmpty(browserSettings.ProfileUserName) ||
false ==
string.IsNullOrEmpty(browserSettings.ProfileName))
2182 firefoxArguments.Add($
"-profile \"{(browserSettings.ProfileName ?? browserSettings.ProfileUserName).TrimEnd('\\')}\"");
2183 else if (
false ==
string.IsNullOrEmpty(browserSettings.ProfileDataDirectory))
2184 firefoxArguments.Add($
"-profile \"{browserSettings.ProfileDataDirectory.TrimEnd('\\')}\"");
2186 chromeArguments = firefoxArguments;
2189 bool holdingHandshakeGate =
false;
2190 UdpClient udpHandshake =
null;
2198 if (
null != URL &&
false ==
string.IsNullOrEmpty(URL.Url))
2199 chromeArguments.Add(URL?.Url);
2201 if (AutomationEngine.PuppeteerPipe == browserSettings.AutomationEngine || AutomationEngine.PuppeteerPipeHW == browserSettings.AutomationEngine)
2202 browserSettings.DebugPipe =
true;
2204 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Running workflow for Automation Engine [{browserSettings.AutomationEngine}]",
null, GPALObjectType.None);
2206 if (
true == browserSettings.UseOttoMagic)
2210 try { holdingHandshakeGate = ottoHandshakeGate.WaitOne(OttoHandshakeGateTimeoutMs); }
2211 catch (AbandonedMutexException) { holdingHandshakeGate =
true; }
2213 if (
false == holdingHandshakeGate)
2214 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Waited [{OttoHandshakeGateTimeoutMs}] ms for another OttoMagic browser to finish its port handshake. Launching anyway, this browser may not get a port of its own.", browserSettings, GPALObjectType.Browser);
2216 udpHandshake =
new UdpClient(47623);
2217 udpHandshake.Client.ReceiveTimeout = 30000;
2219 KeepFromChildren(udpHandshake);
2222 int firefoxLauncherPid = 0;
2224 if (
true == browserSettings.DebugPipe)
2226 var sa =
new Native.SECURITY_ATTRIBUTES
2228 nLength = Marshal.SizeOf<Native.SECURITY_ATTRIBUTES>(),
2230 lpSecurityDescriptor = IntPtr.Zero
2233 IntPtr hProcess = IntPtr.Zero;
2234 IntPtr reservedPtr = IntPtr.Zero;
2235 AnonymousPipeServerStream stdinPipe =
null, stdoutPipe =
null, stderrPipe =
null;
2240 stdinPipe =
new AnonymousPipeServerStream(PipeDirection.Out, HandleInheritability.Inheritable, 1048576);
2241 stdoutPipe =
new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.Inheritable, 1048576);
2242 stderrPipe =
new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.Inheritable, 1048576);
2245 var chromeCommandLine =
new StringBuilder();
2246 chromeCommandLine.Append(
$@"""{browserPath}""");
2247 chromeCommandLine.Append(
" ");
2248 chromeCommandLine.Append(
string.Join(
" ", chromeArguments));
2251 var startupInfo =
new Native.STARTUPINFO
2253 cb = (uint)Marshal.SizeOf<Native.STARTUPINFO>(),
2256 lpDesktop =
true == browserSettings.HiddenDesktop ? HiddenDesktop.StartupNameFor(browserSettings.HiddenDesktopName) :
null,
2257 dwFlags = Native.STARTF_USESHOWWINDOW | Native.STARTF_USESTDHANDLES,
2258 wShowWindow = (ushort)(headless ? Native.SW_HIDE : Native.SW_SHOWNORMAL),
2259 hStdInput = stdinPipe.ClientSafePipeHandle.DangerousGetHandle(),
2260 hStdOutput = stdoutPipe.ClientSafePipeHandle.DangerousGetHandle(),
2261 hStdError = stderrPipe.ClientSafePipeHandle.DangerousGetHandle(),
2267 created = Native.CreateProcess(
2269 chromeCommandLine.ToString(),
2275 browserSettings.ProfileDataDirectory,
2277 out var processInfo2);
2280 throw new Win32Exception(Marshal.GetLastWin32Error(),
"PuppeteerPipe CreateProcess failed.");
2282 hProcess = processInfo2.hProcess;
2285 browserSettings.InboundPipe = stdinPipe;
2286 browserSettings.OutboundPipe = stdoutPipe;
2287 browserSettings.ErrorPipe = stderrPipe;
2291 var errorReader =
new StreamReader(stderrPipe, Encoding.UTF8,
true, 4096,
false);
2292 _ = Task.Run(async () =>
2296 char[] buffer =
new char[4096];
2298 while ((read = await errorReader.ReadAsync(buffer, 0, buffer.Length)) > 0)
2300 stderr +=
new string(buffer, 0, read);
2302 if (Native.GetExitCodeProcess(hProcess, out uint finalExitCode) && finalExitCode != 0)
2304 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"Chrome exited with code: [{finalExitCode}], Stderr: [{stderr}]", browserSettings, GPALObjectType.Other);
2307 catch (GPALException)
2311 catch (Exception ex)
2313 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Error reading stderr", browserSettings, GPALObjectType.Other, ex);
2317 errorReader.Dispose();
2324 if (!Native.GetExitCodeProcess(hProcess, out uint exitCode))
2325 throw new Win32Exception(Marshal.GetLastWin32Error(),
"Failed to get process exit code.");
2326 if (exitCode != Native.STILL_ACTIVE)
2327 throw new GPALException($
"Chrome exited. Exit code: {exitCode}, Stderr: {stderr}, Check {Path.Combine(browserSettings.ProfileDataDirectory, "chrome_log.txt
")}");
2329 browserSettings.Process = Process.GetProcessById((
int)processInfo2.dwProcessId);
2330 browserSettings.Process.EnableRaisingEvents =
true;
2332 catch (ArgumentException ex)
2334 if (Native.GetExitCodeProcess(hProcess, out uint exitCode) && exitCode != Native.STILL_ACTIVE)
2335 throw new GPALException($
"Chrome exited early. Exit code: {exitCode}, Stderr: {stderr}");
2336 throw new GPALException($
"Failed to get Chrome process by ID: {ex.Message}, Stderr: {stderr}");
2339 catch (GPALException)
2343 catch (Exception ex)
2345 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Failed to launch Chrome with pipes", browserSettings, GPALObjectType.Other, ex);
2350 if (reservedPtr != IntPtr.Zero)
2351 Marshal.FreeHGlobal(reservedPtr);
2352 if (hProcess != IntPtr.Zero)
2353 Native.CloseHandle(hProcess);
2356 stdinPipe?.Dispose();
2357 stdoutPipe?.Dispose();
2358 stderrPipe?.Dispose();
2362 else if (
true == browserSettings.HiddenDesktop)
2368 Native.STARTUPINFO hiddenStartup =
new Native.STARTUPINFO
2370 cb = (uint)Marshal.SizeOf<Native.STARTUPINFO>(),
2371 lpDesktop = HiddenDesktop.StartupNameFor(browserSettings.HiddenDesktopName)
2374 string hiddenCommandLine =
$@"""{browserPath}"" {string.Join(" ", chromeArguments)}";
2376 if (
false == Native.CreateProcess(
null, hiddenCommandLine, IntPtr.Zero, IntPtr.Zero,
false, 0, IntPtr.Zero,
null, ref hiddenStartup, out var hiddenProcess))
2377 throw new Win32Exception(Marshal.GetLastWin32Error(), $
"Could not start [{browserSettings.BrowserType}] on [{browserSettings.HiddenDesktopName}]");
2379 Native.CloseHandle(hiddenProcess.hProcess);
2380 Native.CloseHandle(hiddenProcess.hThread);
2382 browserSettings.Process = Process.GetProcessById((
int)hiddenProcess.dwProcessId);
2385 if (BrowserType.FireFox == browserSettings.BrowserType)
2386 firefoxLauncherPid = browserSettings.Process?.Id ?? 0;
2390 browserSettings.Process = Process.Start(
new ProcessStartInfo
2392 FileName = browserPath,
2393 Arguments =
string.Join(
" ", chromeArguments),
2394 UseShellExecute =
false,
2398 RedirectStandardOutput =
true,
2399 RedirectStandardError =
true
2405 browserSettings.Process.OutputDataReceived += (sender, data) => PublishBrowserOutput(browserSettings, data);
2406 browserSettings.Process.ErrorDataReceived += (sender, data) => PublishBrowserOutput(browserSettings, data);
2407 browserSettings.Process.BeginOutputReadLine();
2408 browserSettings.Process.BeginErrorReadLine();
2411 if (BrowserType.FireFox == browserSettings.BrowserType)
2412 firefoxLauncherPid = browserSettings.Process?.Id ?? 0;
2414 DateTime deadline = DateTime.UtcNow + timeout;
2419 if (BrowserType.FireFox == browserSettings.BrowserType)
2420 AdoptFirefoxChild(browserSettings, firefoxLauncherPid, deadline);
2425 if (
true == headless)
2427 if (
true == browserSettings.Process.HasExited)
2429 string msg = $
"[{browserSettings.BrowserType}] did not start. Exiting.";
2430 GPAL.PublishSimpleEvent(GPALEventType.ERROR, msg);
2431 throw new TimeoutException(msg);
2436 while (browserSettings.Process.MainWindowHandle == IntPtr.Zero && !browserSettings.Process.HasExited)
2438 if (DateTime.UtcNow > deadline)
2440 string msg = $
"[{browserSettings.BrowserType}] did not start in [{secondsToWait}] seconds. Exiting.";
2441 GPAL.PublishSimpleEvent(GPALEventType.ERROR, msg);
2442 throw new TimeoutException(msg);
2452 if (
false == browserSettings.WindowSize.IsEmpty &&
2453 false == browserSettings.FullScreen &&
2454 false == browserSettings.Maximize &&
2455 false == browserSettings.Minimize)
2457 browserSettings.Process.Refresh();
2458 WindowHelper.ResizeWindow(browserSettings.Process, browserSettings.WindowSize);
2463 if (
true == browserSettings.UseOttoMagic)
2467 var remoteEp =
new IPEndPoint(IPAddress.Any, 0);
2470 int launchedPid = browserSettings.Process?.Id ?? 0;
2477 while (0 == restPort)
2483 try { data = udpHandshake.Receive(ref remoteEp); }
2484 catch (SocketException) when (0 != firstHeard)
2486 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"No GPALRestAPI traced back to this browser, so port [{firstHeard}] is being used on the strength of it being the first one announced", browserSettings, GPALObjectType.Browser);
2488 restPort = firstHeard;
2492 string[] said = Encoding.UTF8.GetString(data).Trim().Split(
' ');
2498 if (2 > said.Length ||
false ==
int.TryParse(said[0], out
int announced) ||
false ==
int.TryParse(said[1], out
int hostPid))
2499 throw new InvalidOperationException($
"GPALRestAPI announced [{string.Join(" ", said)}], which is not a port and a process id");
2501 if (0 == launchedPid ||
true == BrowserHelper.CameFromProcess(hostPid, launchedPid))
2502 restPort = announced;
2505 if (0 == firstHeard)
2506 firstHeard = announced;
2508 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"Port [{announced}] was announced by another browser's GPALRestAPI [{hostPid}], so this launch is still waiting for its own", browserSettings, GPALObjectType.Browser);
2512 browserSettings.RestApiBaseUrl = $
"http://localhost:{restPort}/";
2513 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"GPALRestAPI ready on port [{restPort}].", browserSettings, GPALObjectType.Browser);
2515 catch (GPALException)
2519 catch (Exception ex)
2521 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"GPALRestAPI never announced a port, so this browser cannot reach it", browserSettings, GPALObjectType.Browser, ex);
2526 udpHandshake?.Close();
2530 if (
true == holdingHandshakeGate)
2532 ottoHandshakeGate.ReleaseMutex();
2533 holdingHandshakeGate =
false;
2540 if (firefoxLauncherPid > 0 &&
2541 (browserSettings.Process ==
null || browserSettings.Process.HasExited))
2545 using (var searcher =
new ManagementObjectSearcher(
2546 $
"SELECT ProcessId FROM Win32_Process WHERE ParentProcessId = {firefoxLauncherPid} AND Name = 'firefox.exe'"))
2548 foreach (ManagementObject obj
in searcher.Get())
2550 int childPid = Convert.ToInt32(obj[
"ProcessId"]);
2551 try { browserSettings.Process = System.Diagnostics.Process.GetProcessById(childPid);
break; }
2559 else if (
true == browserSettings.UsePuppeteer)
2561 _ = browserSettings.PuppeteerCommunicator;
2565 if (browserSettings.Maximize)
2569 if (
true == browserSettings.UseOttoMagic)
2571 browser.MagicHelper.Client.Maximize().Execute();
2572 else if (
true == browserSettings.UsePuppeteer)
2573 browserSettings.PuppeteerCommunicator.Maximize().GetAwaiter().GetResult();
2575 catch (GPALException)
2579 catch (Exception ex)
2581 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Failed to maximize browser window", browserSettings, GPALObjectType.Other, ex);
2584 else if (browserSettings.Minimize)
2588 if (
true == browserSettings.UseOttoMagic)
2589 browser.MagicHelper.Client.Minimize().Execute();
2590 else if (
true == browserSettings.UsePuppeteer)
2591 browserSettings.PuppeteerCommunicator.Minimize().GetAwaiter().GetResult();
2593 catch (GPALException)
2597 catch (Exception ex)
2599 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Failed to minimize browser window", browserSettings, GPALObjectType.Other, ex);
2602 else if (browserSettings.FullScreen)
2606 if (
true == browserSettings.UseOttoMagic)
2607 browser.MagicHelper.Client.FullScreen().Execute();
2608 else if (
true == browserSettings.UsePuppeteer)
2609 browserSettings.PuppeteerCommunicator.FullScreen().GetAwaiter().GetResult();
2611 catch (GPALException)
2615 catch (Exception ex)
2617 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Failed to full screen browser window", browserSettings, GPALObjectType.Other, ex);
2623 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"Browser [{browserSettings.BrowserType}] is process [{browserSettings.Process?.Id}] from [{browserPath}] on port [{browserSettings.DebugPort}] at [{browserSettings.PuppeteerUrl}]", browserSettings, GPALObjectType.Other);
2625 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Browser [{browserSettings.BrowserType}] launched.", browserSettings, GPALObjectType.Other);
2627 catch (GPALException)
2631 catch (Exception ex)
2636 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Unable to launch browser [{browserSettings.BrowserType}] for URL [{URL}]", browserSettings, GPALObjectType.Other, ex);
2644 udpHandshake?.Close();
2646 if (
true == holdingHandshakeGate)
2647 ottoHandshakeGate.ReleaseMutex();
2650 return browserSettings.Process;
2669 var endpoints =
new[]
2671 (
"people", 83,
"name"),
2672 (
"planets", 60,
"name"),
2673 (
"films", 6,
"title"),
2674 (
"species", 37,
"name"),
2675 (
"vehicles", 39,
"name"),
2676 (
"starships", 36,
"name")
2679 string FetchQuery(
string endpoint,
int maxId,
string field,
int retries = 3)
2681 for (
int attempt = 0; attempt < retries; attempt++)
2683 int id =
new Random().Next(1, maxId + 1);
2684 var response = restClient.WithEndpoint($
"{endpoint}/{id}").Execute<SwapiResponse>(
false);
2685 if (response !=
null)
2687 return field ==
"name" ? response.name : response.title;
2689 Thread.Sleep(100 * (
int)Math.Pow(2, attempt));
2695 var (endpoint, maxId, field) = endpoints[
new Random().Next(endpoints.Length)];
2696 string query = FetchQuery(endpoint, maxId, field);
2701 var fallbackEndpoint = endpoints[
new Random().Next(endpoints.Length)];
2702 query = FetchQuery(fallbackEndpoint.Item1, fallbackEndpoint.Item2, fallbackEndpoint.Item3);
2705 fallbackEndpoint = endpoints[
new Random().Next(endpoints.Length)];
2706 query = FetchQuery(fallbackEndpoint.Item1, fallbackEndpoint.Item2, fallbackEndpoint.Item3) ??
"star wars";
2710 if (
new Random().NextDouble() > 0.4)
2712 var (otherEndpoint, otherMaxId, otherField) = endpoints[
new Random().Next(endpoints.Length)];
2713 string otherQuery = FetchQuery(otherEndpoint, otherMaxId, otherField);
2714 if (otherQuery !=
null)
2716 query += $
" {otherQuery}";
2720 var fallbackEndpoint = endpoints[
new Random().Next(endpoints.Length)];
2721 otherQuery = FetchQuery(fallbackEndpoint.Item1, fallbackEndpoint.Item2, fallbackEndpoint.Item3);
2722 query += otherQuery !=
null ? $
" {otherQuery}" :
"";
2729 private class SwapiResponse
2731 public string name {
get;
set; }
2732 public string title {
get;
set; }
2735 public static string GetFullUrl(
string url, IBrowser browser, out
bool areRobotsAllowed,
bool noRobotsCheck =
false)
2737 List<string> robotsTxt =
new List<string>();
2738 string normalized = url;
2739 areRobotsAllowed =
true;
2744 if (!Uri.TryCreate(url, UriKind.Absolute, out _))
2746 normalized =
"https://" + url;
2750 if (
false == noRobotsCheck &&
true == ((Browser)browser).BrowserSettings.ObeyRobotsTxt &&
false == UrlHelper.IsNonHtmlResource(normalized))
2751 areRobotsAllowed = UrlHelper.CheckRobotsTxt(normalized, browser, robotsTxt);
2753 if (
true == url.StartsWith(
"https://"))
2756 if (
string.IsNullOrWhiteSpace(url) || UrlHelper.AreEquivalent(url,
"https://google.com"))
2757 return "https://google.com";
2760 catch (GPALException)
2764 catch (Exception ex)
2766 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Error getting full url", browser, GPALObjectType.Other, ex);
2782 var attrList = attributes.OrderBy(kvp => kvp.Key)
2783 .Select(kvp => $
"{kvp.Key}={kvp.Value}")
2787 string str =
string.Join(
"|", attrList);
2791 foreach (
char c
in str)
2794 hash = ((hash << 5) - hash + c) | 0;
2810 string chromeVersion;
2812 string firefoxVersion;
2813 string retval =
null;
2815 if (BrowserType.Chrome == browserType)
2817 DriverHelper.GetChromeVersion(out chromeVersion);
2819 if (
null != chromeVersion && 0 < chromeVersion.IndexOf(
'.'))
2820 chromeVersion = chromeVersion.AsSpan().Slice(0, chromeVersion.IndexOf(
'.')).ToString();
2821 retval = String.Format(
GPAL.GPALSettings.ChromeUserAgentTemplate, chromeVersion);
2823 else if (BrowserType.Edge == browserType)
2825 DriverHelper.GetEdgeVersion(out edgeVersion);
2827 if (
null != edgeVersion && 0 < edgeVersion.IndexOf(
'.'))
2828 edgeVersion = edgeVersion.AsSpan().Slice(0, edgeVersion.IndexOf(
'.')).ToString();
2829 retval = String.Format(
GPAL.GPALSettings.EdgeUserAgentTemplate, edgeVersion);
2831 else if (BrowserType.FireFox == browserType)
2833 DriverHelper.GetFirefoxVersion(out firefoxVersion);
2835 if (
null != firefoxVersion && 0 < firefoxVersion.IndexOf(
'.'))
2836 firefoxVersion = firefoxVersion.AsSpan().Slice(0, firefoxVersion.IndexOf(
'.')).ToString();
2837 retval = String.Format(
GPAL.GPALSettings.FirefoxUserAgentTemplate, firefoxVersion);
2851 private static void KeepFromChildren(UdpClient client)
2853 if (
false == Native.SetHandleInformation(client.Client.Handle, Native.HANDLE_FLAG_INHERIT, 0))
2855 $
"Could not take the inherit flag off the handshake socket, so the next OttoMagic launch may find [47623] still held",
2856 null, GPALObjectType.Browser,
new Win32Exception(Marshal.GetLastWin32Error()));
2859 internal string GetUserAgent(BrowserSettings browserSettings, List<string> chromeArguments)
2861 string userAgent =
null;
2862 string browserPath =
null;
2867 if (
false == browserSettings.UserAgentFromBrowser)
2868 return BrowserHelper.ResolveUserAgent(browserSettings);
2870 GPAL.
PublishSimpleEvent(GPALEventType.INFO,
"Getting user agent for headless use.", chromeArguments, GPALObjectType.Other);
2873 switch (browserSettings.BrowserType)
2875 case BrowserType.Chrome:
2876 browserPath = Puppeteer.GetChromePath();
2878 case BrowserType.Edge:
2879 browserPath = Puppeteer.GetEdgePath();
2881 case BrowserType.FireFox:
2882 browserPath = Puppeteer.GetFirefoxPath();
2885 Exception ex =
new Exception(
"Unsupported Browser Type");
2886 GPAL.
PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Unsupported browser type [{browserSettings.BrowserType}]", browser, GPALObjectType.Browser, ex);
2890 UdpClient probeHandshake =
null;
2891 bool holdingProbeGate =
false;
2895 List<string> ourArgs =
new List<string>(chromeArguments);
2898 if (
null == browserSettings.DebugPort)
2899 browserSettings.DebugPort = BrowserHelper.FindFreePort();
2901 if (BrowserType.FireFox != browserSettings.BrowserType)
2902 ourArgs.Add($
"--remote-debugging-port={browserSettings.DebugPort.Value}");
2908 try { holdingProbeGate = ottoHandshakeGate.WaitOne(OttoHandshakeGateTimeoutMs); }
2909 catch (AbandonedMutexException) { holdingProbeGate =
true; }
2911 probeHandshake =
new UdpClient(47623);
2912 probeHandshake.Client.ReceiveTimeout = 30000;
2914 KeepFromChildren(probeHandshake);
2917 browserSettings.Process = Process.Start(
new ProcessStartInfo
2919 FileName = browserPath,
2920 Arguments =
string.Join(
" ", ourArgs),
2921 UseShellExecute =
false
2927 if (BrowserType.FireFox == browserSettings.BrowserType)
2929 var probeEndPoint =
new IPEndPoint(IPAddress.Any, 0);
2930 string probePortText = Encoding.UTF8.GetString(probeHandshake.Receive(ref probeEndPoint)).Trim();
2933 userAgent =
true ==
int.TryParse(probePortText, out
int probePort)
2934 ? ((IRESTClient)GPAL.RESTClient.WithAPIBase($
"http://localhost:{probePort}/").ToGPALObject()).GetUserAgent().Execute()
2935 : Client.GetUserAgent().Execute();
2939 AutomationEngine aeSave = browserSettings.AutomationEngine;
2940 browserSettings.AutomationEngine = AutomationEngine.PuppeteerPort;
2941 _ = browserSettings.PuppeteerCommunicator;
2942 userAgent = browserSettings.PuppeteerClient.GetUserAgent().Execute();
2943 browserSettings.AutomationEngine = aeSave;
2945 userAgent = userAgent.Replace(
"Headless",
"");
2949 var parameters =
new Dictionary<string, object>();
2952 browserSettings.PuppeteerCommunicator._readerReadyTcs?.SetCanceled();
2953 if (
false == browserSettings.PuppeteerCommunicator._usePipes)
2954 browserSettings.PuppeteerCommunicator.CloseOutputWebSocketAsync().GetAwaiter();
2965 var psi =
new ProcessStartInfo
2967 FileName =
"taskkill",
2968 Arguments = $
"/PID {browserSettings.Process.Id} /F /T",
2969 CreateNoWindow =
true,
2970 UseShellExecute =
false
2972 Process.Start(psi).WaitForExit();
2976 Thread.Sleep(1_000);
2978 if (
null != browserSettings.Process &&
false == browserSettings.Process.HasExited)
2982 browserSettings.Process.Kill();
2983 browserSettings.Process.Close();
2988 Thread.Sleep(1_000);
2992 browserSettings.PuppeteerClient =
null;
2993 browserSettings.PuppeteerCommunicator =
null;
2994 browserSettings.Process =
null;
2997 catch (GPALException)
3001 catch (Exception ex)
3004 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Failed to fetch user-agent using [{userAgent}].", browserSettings, GPALObjectType.Other, ex);
3008 probeHandshake?.Close();
3011 if (
true == holdingProbeGate)
3012 ottoHandshakeGate.ReleaseMutex();
3015 return false ==
string.IsNullOrEmpty(userAgent) ? userAgent :
GetUserAgentString(browserSettings.BrowserType);