253 internal MagicHelper(
Browser browser)
255 this.browser = browser;
263 private static readonly Mutex ottoHandshakeGate =
new Mutex(
false,
"GPAL.OttoMagic.PortHandshake");
264 private const int OttoHandshakeGateTimeoutMs = 60_000;
266 List<string> lastErrorMessage =
new List<string>();
267 bool supressedMessage =
false;
268 string lastSessionToken;
269 IBrowser browser {
get;
set; } =
null;
272 private string _clientApiBase;
292 if (
false ==
string.IsNullOrEmpty(url) && url != _clientApiBase)
294 _client.WithAPIBase(url);
295 _clientApiBase = url;
309 Client.Back().Execute();
312 public string CaptureVisibleTab()
314 return Client.CaptureVisibleTab().Execute();
328 public string CheckNetworkIdle(
int? maxConnections =
null,
int? timeoutMs =
null,
int? pruneMs =
null)
334 string retVal = Client
335 .WithEndpoint(ApiEndpoint.CheckNetworkIdle)
336 .WithMaxConnections(maxConnections ?? settings?.NetworkIdleMaxConnections ?? 0)
337 .WithTimeoutMs(timeoutMs ?? settings?.NetworkIdleTimeoutMs ?? 30_000)
338 .WithPruneMs(pruneMs ?? settings?.NetworkIdlePruneMs ?? 3_000)
352 if (
null == URLorTabId)
353 return Client.CloseTab().Execute<
TabTuple>();
354 else if (URLorTabId is
string v)
355 return Client.CloseTab(v).Execute<
TabTuple>();
356 else if (URLorTabId is
GPALUrl u)
357 return Client.CloseTab(u).Execute<
TabTuple>();
358 else if (URLorTabId is
int i)
359 return Client.CloseTab(URLorTabId).Execute<
TabTuple>();
361 return Client.CloseTab().Execute<
TabTuple>();
375 public bool DeleteStorage(WebsiteStorageType storageType,
bool deleteAcrossOrigins =
false,
string domain =
null,
string path =
null,
string key =
null,
string storeName =
null)
379 fluentObject = Client.DeleteStorage(storageType);
381 if (
false ==
string.IsNullOrEmpty(domain))
382 fluentObject.WithStorageDomain(domain);
384 if (
false ==
string.IsNullOrEmpty(path))
385 fluentObject.WithStoragePath(path);
387 if (
false ==
string.IsNullOrEmpty(key))
388 fluentObject.WithStorageKey(key);
390 if (
false ==
string.IsNullOrEmpty(storeName))
391 fluentObject.WithStorageStoreName(storeName);
393 fluentObject.WithDeleteAcrossOrigins(deleteAcrossOrigins);
395 fluentObject.Execute();
408 public string GetStorage(WebsiteStorageType storageType,
string domain =
null,
string path =
null,
string key =
null,
string storeName =
null)
412 fluentObject = Client.GetStorage(storageType);
414 if (
false ==
string.IsNullOrEmpty(domain))
415 fluentObject.WithStorageDomain(domain);
417 if (
false ==
string.IsNullOrEmpty(path))
418 fluentObject.WithStoragePath(path);
420 if (
false ==
string.IsNullOrEmpty(key))
421 fluentObject.WithStorageKey(key);
423 if (
false ==
string.IsNullOrEmpty(storeName))
424 fluentObject.WithStorageStoreName(storeName);
426 return fluentObject.Execute();
429 public bool SetStorage(WebsiteStorageType storageType,
string data,
string domain =
null,
string path =
null,
string key =
null,
string storeName =
null)
434 fluentObject = Client.SetStorage(storageType);
436 if (
false ==
string.IsNullOrEmpty(domain))
437 fluentObject.WithStorageDomain(domain);
439 if (
false ==
string.IsNullOrEmpty(path))
440 fluentObject.WithStoragePath(path);
442 if (
false ==
string.IsNullOrEmpty(key))
443 fluentObject.WithStorageKey(key);
445 if (
false ==
string.IsNullOrEmpty(storeName))
446 fluentObject.WithStorageStoreName(storeName);
448 if (
false ==
string.IsNullOrEmpty(data))
449 fluentObject.WithData(data);
451 fluentObject.Execute<
bool>();
462 Client.Forward().Execute();
471 Client.FullScreen().Execute();
482 bool areRobotsAllowed =
true;
484 URL.
ForUrl(GetFullUrl(URL?.Url, browser, out areRobotsAllowed));
487 ((
Browser)browser)._areRobotsAllowed = areRobotsAllowed;
489 var result = Client.GoTo(URL?.Url).Execute();
490 browser.ServerResponseCode = ((
RESTClient)Client).StatusCode;
503 return Client.GetContentAndCss(elementId).Execute();
505 public Dictionary<string, object> GetCssAttributes(
string elementId)
507 return Client.GetCssAttributes(elementId).Execute<Dictionary<string, object>>();
509 public Dictionary<string, object> GetDomAttributes(
string elementId)
511 return Client.GetDomAttributes(elementId).Execute<Dictionary<string, object>>();
513 public Dictionary<string, object> GetDomProperties(
string elementId)
515 return Client.GetDomProperties(elementId).Execute< Dictionary<string, object>>();
525 return Client.GetCurrentUrl().Execute();
536 string retVal =
null;
538 if (lastSessionToken != sessionToken)
540 lastErrorMessage.Clear();
541 supressedMessage =
false;
542 lastSessionToken = sessionToken;
545 dynamic obj = Client.GetReadyStatus().Execute();
546 if (
"null" != obj &&
"NOTOK" != obj)
547 retVal = obj ??
@"""loading""";
551 string currentErrorMessage = $
"Document.Ready status [{retVal}]";
553 if (
false == lastErrorMessage.Contains(currentErrorMessage))
555 lastErrorMessage.Add(currentErrorMessage);
557 supressedMessage =
false;
559 else if (
false == supressedMessage)
561 GPAL.
PublishSimpleEvent(GPALEventType.INFO,
"Supressing repeat messages", currentErrorMessage, GPALObjectType.Other);
562 supressedMessage =
true;
588 URL =
new GPALUrl(
"google.com");
590 bool areRobotsAllowed =
true;
592 URL.
ForUrl(GetFullUrl(URL.
Url, browser, out areRobotsAllowed));
595 ((
Browser)browser)._areRobotsAllowed = (
bool) areRobotsAllowed;
598 browser.ServerResponseCode = ((
RESTClient)Client).StatusCode;
614 browser.ServerResponseCode = ((
RESTClient)Client).StatusCode;
617 else if (URLorTabId is
TabTuple tuple)
620 browser.ServerResponseCode = ((
RESTClient)Client).StatusCode;
625 GPAL.
PublishSimpleEvent(GPALEventType.ERROR, $
"URLorTabId is unknown type [{URLorTabId.GetType()}]",
this, GPALObjectType.Other);
637 Client.SwitchToElement(elementId).Execute();
647 Client.SwitchToElement(elementId).Execute();
656 Client.Minimize().Execute();
665 Client.Maximize().Execute();
675 Client.ScrollIntoView(elementId).Execute();
676 Client.Hover(elementId).Execute();
685 Client.Normal().Execute();
699 URL.ForUrl(GetFullUrl(URL.Url, browser, out ((
Browser)browser)._areRobotsAllowed));
702 browser.ServerResponseCode = ((
RESTClient)Client).StatusCode;
714 return Client.NextTab().Execute<
TabTuple>();
725 return Client.TabCount().Execute<
int>();
745 Client.OverrideReferrer(referrer).Execute();
755 Client.SetUserAgent(userAgent).Execute();
764 Client.PageDown().Execute();
773 Client.PageEnd().Execute();
782 Client.PageTop().Execute();
791 Client.PageUp().Execute();
799 Client.PressModifierKey(modifierKeys).Execute();
808 return Client.PreviousTab().Execute<
TabTuple>();
819 return Client.PreviousWindow().Execute<
WindowTuple>();
827 Client.Refresh().Execute();
836 Client.ReleaseModifierKey(modifierKeys).Execute();
845 Client.Restore().Execute();
855 Client.ScrollWindowByHorizontal(pixels).Execute();
865 Client.ScrollWindowByVertical(pixels).Execute();
874 Client.StealthOverrideReferrer().Execute();
888 Client.SetDownloadFilename(Path.GetFileName(downloadPath)).Execute();
898 return Client.WindowScreenLeft().Execute<
int>();
908 return Client.WindowScreenTop().Execute<
int>();
918 Client.Focus(elementId).Execute();
928 Client.FireChangeEvent(elementId).Execute();
937 Rectangle rect =
new Rectangle();
938 var result = Client.GetWindowRectangle().Execute<Dictionary<string, object>>();
939 Dictionary<string, object> obj =
null;
943 obj = (Dictionary<string, object>)result;
945 var left = obj.ContainsKey(
"Left") ? Convert.ToInt32(obj[
"Left"]) : obj.ContainsKey(
"left") ? Convert.ToInt32(obj[
"left"]) : obj.ContainsKey(
"X") ? Convert.ToInt32(obj[
"X"]) : obj.ContainsKey(
"x") ? Convert.ToInt32(obj[
"x"]) : 0;
946 var top = obj.ContainsKey(
"Top") ? Convert.ToInt32(obj[
"Top"]) : obj.ContainsKey(
"top") ? Convert.ToInt32(obj[
"top"]) : obj.ContainsKey(
"Y") ? Convert.ToInt32(obj[
"Y"]) : obj.ContainsKey(
"y") ? Convert.ToInt32(obj[
"y"]) : 0;
947 var width = obj.ContainsKey(
"Width") ? Convert.ToInt32(obj[
"Width"]) : obj.ContainsKey(
"width") ? Convert.ToInt32(obj[
"width"]) : 0;
948 var height = obj.ContainsKey(
"Height") ? Convert.ToInt32(obj[
"Height"]) : obj.ContainsKey(
"height") ? Convert.ToInt32(obj[
"height"]) : 0;
950 rect =
new Rectangle(left, top, width, height);
962 Client.ScrollIntoView(elementId).Execute();
973 Client.ScrollElement(elementId).WithHPixels(hPixels).WithVPixels(vPixels).Execute();
983 return Client.GetElementAttributeHash(elementId).Execute();
993 List<GPALElement> tmpElements = Client.GetOptions(elementId).Execute<List<GPALElement>>();
1003 return Client.GetPageSource().Execute();
1013 return Client.GetParentNode(elementId).Execute<List<GPALElement>>()[0];
1025 return Client.GetAttribute(elementId).WithAttribute(attribute).Execute();
1037 Client.SetAttribute(elementId).WithAttribute(attribute).WithValue(value).Execute();
1040 public void SetRange(
string elementId,
int rangeValue)
1042 Client.SetRange(elementId).WithRangeValue(rangeValue).Execute();
1053 Client.WithElementId(srcSelector).SetValueFrom(destElementId).Execute();
1063 Client.LeftClick(elementId).Execute();
1074 Client.LeftDoubleClick(elementId).Execute();
1084 Client.MiddleClick(elementId).Execute();
1095 Client.RightClick(elementId).Execute();
1098 public void SelectClick(
string elementId, dynamic indexOrValue)
1100 if (indexOrValue is
int idx)
1101 Client.SelectClick(elementId).WithSelectIndex(idx).Execute();
1103 Client.SelectClick(elementId).WithSelectValue(indexOrValue).Execute();
1114 return Client.IsVisibleInViewport(elementId).Execute<
bool>();
1124 return Client.IsEndOfPage().Execute<
bool>();
1134 Client.Hover(elementId).Execute();
1146 public void DragAndDrop(
string elementId,
int deltaX,
int deltaY,
int offsetX = 0,
int offsetY = 0)
1148 Client.DragAndDrop(elementId).WithDeltaX(deltaX).WithDeltaY(deltaY).WithOffsetX(offsetX).WithOffsetY(offsetY).Execute();
1158 return Client.WindowInnerHeight().Execute<
int>();
1168 return Client.WindowInnerWidth().Execute<
int>();
1178 return Client.WindowOuterHeight().Execute<
int>();
1188 return Client.WindowOuterWidth().Execute<
int>();
1203 public string Fetch(
string url,
string method =
null,
string body =
null,
string contentType =
null,
string[] headers =
null,
bool asBytes =
false)
1205 string retVal = Client
1209 .WithContentType(contentType)
1210 .WithHeaders(headers)
1216 return "NOTOK" == retVal ||
"null" == retVal ? null : retVal;
1226 return Client.WindowPageOffsetX().Execute<
int>();
1236 return Client.WindowPageOffsetY().Execute<
int>();
1254 return true == Client.ElementFromPoint(elementId, x, y).Execute<
bool>();
1257 public Rectangle GetBoundingClientRect(
string elementId)
1259 Rectangle rect =
new Rectangle(0,0,0,0);
1260 var result = Client.GetBoundingClientRect(elementId).Execute<Dictionary<string, object>>();
1261 Dictionary<string, object> obj =
null;
1265 obj = (Dictionary<string, object>)result;
1267 var left = obj.ContainsKey(
"Left") ? Convert.ToInt32(obj[
"Left"]) : obj.ContainsKey(
"left") ? Convert.ToInt32(obj[
"left"]) : obj.ContainsKey(
"X") ? Convert.ToInt32(obj[
"X"]) : obj.ContainsKey(
"x") ? Convert.ToInt32(obj[
"x"]) : 0;
1268 var top = obj.ContainsKey(
"Top") ? Convert.ToInt32(obj[
"Top"]) : obj.ContainsKey(
"top") ? Convert.ToInt32(obj[
"top"]) : obj.ContainsKey(
"Y") ? Convert.ToInt32(obj[
"Y"]) : obj.ContainsKey(
"y") ? Convert.ToInt32(obj[
"y"]) : 0;
1269 var width = obj.ContainsKey(
"Width") ? Convert.ToInt32(obj[
"Width"]) : obj.ContainsKey(
"width") ? Convert.ToInt32(obj[
"width"]) : 0;
1270 var height = obj.ContainsKey(
"Height") ? Convert.ToInt32(obj[
"Height"]) : obj.ContainsKey(
"height") ? Convert.ToInt32(obj[
"height"]) : 0;
1272 rect =
new Rectangle(left, top, width, height);
1285 Client.HideElement(elementId).Execute();
1295 string answered = Client.InjectScript(script).Execute();
1300 if (
true == answered?.Contains(
"error"))
1301 GPAL.
PublishSimpleEvent(GPALEventType.WARNING, $
"The extension did not register the script [{answered}]",
this, GPALObjectType.Other);
1314 return Client.ExecuteJavaScript(script).Execute();
1323 Client.ClearInjectedScripts().Execute();
1336 return Client.IsClickable(elementId).Execute<
bool>();
1348 return Client.IsDisplayed(elementId).Execute<
bool>();
1360 return Client.IsEnabled(elementId).Execute<
bool>();
1368 Client.ClearReferrer().Execute();
1379 return Client.Evaluate(xpath).Execute<
GPALElement>();
1390 return Client.EvaluateAll(xpath).Execute<List<GPALElement>>();
1400 return Client.EvaluatePersistent(xpath).Execute<
GPALElement>();
1411 return Client.EvaluateAllPersistent(xpath).Execute<List<GPALElement>>();
1421 return Client.GetUserAgent().Execute();
1431 return Client.GetLanguages().Execute();
1443 public void CaptureCalls(
bool capture =
true,
string urlFragment =
null,
bool clear =
false)
1447 if (
false ==
string.IsNullOrWhiteSpace(urlFragment)) capturing.WithCallFilter(urlFragment);
1448 if (
true == clear) capturing.WithClearCalls(
true);
1450 capturing.Execute();
1460 return Client.GetCapturedCalls().Execute();
1471 Client.FillInAppend(elementId).WithText(text).Execute();
1482 Client.FillInInsert(elementId).WithText(text).Execute();
1493 Client.FillInOverwrite(elementId).WithText(text).Execute();
1504 Client.LeftClickAndDownload(elementId).WithDownloadFile(Path.GetFileName(filenameAndPath)).Execute();
1514 Client.LeftClickAndUpload(elementId).WithUploadFile(filenameAndPath).Execute();
1524 Client.LeftClickAndUpload(elementId).WithUploadFiles(filenamesAndPaths).Execute();
1536 var elem = Client.QuerySelector(css).Execute<
GPALElement>();
1548 return Client.QuerySelectors(css).Execute<List<GPALElement>>();
1559 var persist = Client.QueryPersistentSelector(css).Execute<
GPALElement>();
1572 return Client.QueryPersistentSelectors(css).Execute<List<GPALElement>>();
1583 Client.ScrollWindow(hPixels, vPixels).Execute();
1593 Client.SendKey(vkcode).Execute();
1604 Client.SendString(text, delayMs).Execute();
1614 Client.SubmitForm(elementId).Execute();
1623 Client.SwitchToDefaultContent().Execute();
1633 Client.SwitchToShadowRoot(elementId).Execute();
1636 #region Window Handling
1642 URL.ForUrl(GetFullUrl(URL.Url, browser, out ((
Browser)browser)._areRobotsAllowed));
1645 browser.ServerResponseCode = ((
RESTClient)Client).StatusCode;
1650 public WindowTuple GoToWindow(dynamic urlOrId)
1652 if (urlOrId is
string v)
1653 return Client.GoToWindow(v).Execute<WindowTuple>();
1655 return Client.GoToWindow(urlOrId.ToString()).Execute<WindowTuple>();
1664 return Client.CloseBrowser().Execute();
1669 if (urlOrId is
string v)
1670 return Client.CloseWindow(v).Execute<
WindowTuple>();
1672 return Client.CloseWindow(urlOrId.ToString()).Execute<
WindowTuple>();
1675 public WindowTuple GetCurrentWindow()
1677 return Client.GetCurrentWindow().Execute<WindowTuple>();
1679 #endregion Window Handling
1688 return Client.GetBrowserSettings().Execute();
1698 return Client.GetGpalSettings().Execute();
1708 return Client.GetSettings().Execute();
1718 return Client.GetWorkflow().Execute();
1720 #endregion GPAL Studio
1738 static void PublishBrowserOutput(
BrowserSettings browserSettings, DataReceivedEventArgs data)
1740 if (
null != data.Data)
1741 GPAL.
PublishSimpleEvent(GPALEventType.DEEPDEBUG, $
"[{browserSettings.BrowserType}] {data.Data}", browserSettings, GPALObjectType.Other);
1743 public Process LaunchBrowser(
Browser browser, GPALUrl URL, List<string> additionalArguments =
null)
1745 BrowserSettings browserSettings = browser.BrowserSettings;
1746 bool created =
false;
1747 int secondsToWait = 30;
1748 TimeSpan timeout = TimeSpan.FromSeconds(secondsToWait);
1750 if (
null == browserSettings)
1753 GPAL.
PublishSimpleEvent(GPALEventType.ERROR,
"BrowserSettings parameter is null. Using chrome puppeteer port.",
null, GPALObjectType.Other);
1755 browserSettings = ((
Browser)browser).BrowserSettings;
1758 if (
null != browserSettings.ExistingBrowserPort)
1760 foreach (Browser b
in GPAL.Browsers)
1761 if (b != browser && b.BrowserSettings.ExistingBrowserPort == browserSettings.ExistingBrowserPort)
1763 browser.PuppeteerClient = b.PuppeteerClient;
1764 browser.PuppeteerCommunicator = b.PuppeteerCommunicator;
1770 if (
null == URL ||
null == URL?.Url)
1771 URL =
"https://www.google.com";
1773 URL.ForUrl(GetFullUrl(URL?.Url, browser, out ((Browser)browser)._areRobotsAllowed,
true));
1775 if (
false == ((Browser)browser).AreRobotsAllowed &&
true == ((Browser)browser).BrowserSettings.ObeyRobotsTxt)
1777 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);
1778 URL.ForUrl(
"https://google.com");
1781 bool headless = browserSettings.UseHeadless;
1782 var chromeArguments = additionalArguments ??
new List<string>();
1786 switch (browserSettings.BrowserType)
1788 case BrowserType.Chrome:
1789 browserPath = Puppeteer.GetChromePath();
1791 case BrowserType.Edge:
1792 browserPath = Puppeteer.GetEdgePath();
1794 case BrowserType.FireFox:
1795 browserPath = Puppeteer.GetFirefoxPath();
1798 Exception ex =
new Exception(
"Unsupported Browser Type");
1799 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Unsupported browser type [{browserSettings.BrowserType}]", browserSettings, GPALObjectType.Other, ex);
1803 if (
string.IsNullOrEmpty(browserPath) ||
false == File.Exists(browserPath))
1805 string msg = $
"[{browserSettings.BrowserType}] executable not found at [{browserPath}]";
1806 GPAL.PublishSimpleEvent(GPALEventType.ERROR, msg, browserSettings, GPALObjectType.Other);
1807 throw new NotFoundException(msg);
1811 if (browserSettings.BrowserType == BrowserType.Chrome || browserSettings.BrowserType == BrowserType.Edge)
1813 string userAgent =
null;
1816 if (
false ==
string.IsNullOrEmpty(browserSettings.ProfileUserName) ||
false ==
string.IsNullOrEmpty(browserSettings.ProfileName))
1818 browserSettings.ProfileDataDirectory = BrowserHelper.GetBrowserProfileDirectory(browserSettings);
1819 if (
false ==
string.IsNullOrEmpty(browserSettings.ProfileName))
1821 string profileDir = BrowserHelper.FindProfileDirectory(browserSettings.ProfileDataDirectory, browserSettings.ProfileName) ?? browserSettings.ProfileName;
1822 chromeArguments.Add($
"--profile-directory={profileDir}");
1825 else if (
true ==
string.IsNullOrEmpty(browserSettings.ProfileDataDirectory))
1827 browserSettings.ProfileDataDirectory = ChromeProfileManager.CreateTempUserProfile(
1828 browserSettings.DownloadLocation ?? FileHelper.GetDefaultDownloadDirectory(browser),
1829 browserSettings.PromptForDownload,
1830 browserSettings.OpenPDFExternally,
1831 browserSettings.LoadImages,
1832 browserSettings.UseOttoMagic
1834 browserSettings.TempProfileCreated =
true;
1842 if (
false == browserSettings.TempProfileCreated &&
false ==
string.IsNullOrEmpty(browserSettings.ProfileDataDirectory))
1843 browserSettings.PreviousDownloadPreferences = ChromeProfileManager.ApplyDownloadPreferences(
1844 browserSettings.ProfileDataDirectory,
1845 browserSettings.DownloadLocation,
1846 browserSettings.PromptForDownload,
1847 browserSettings.OpenPDFExternally);
1853 if (
false == browserSettings.TempProfileCreated)
1854 BrowserHelper.RefuseIfProfileIsOpen(browserSettings);
1857 if (
true == browserSettings.UsePuppeteer)
1859 if (
true == browserSettings.DebugPipe)
1860 chromeArguments.Add(
"--remote-debugging-pipe --enable-unsafe-extension-debugging --remote-debugging-port=0");
1865 if (
null == browserSettings.DebugPort)
1866 browserSettings.DebugPort = BrowserHelper.FindFreePort();
1868 browserSettings.PuppeteerUrl = $
"http://localhost:{browserSettings.DebugPort}";
1869 chromeArguments.Add($
"--remote-debugging-port={browserSettings.DebugPort.Value}");
1882 if (
false == browserSettings.FullScreen &&
1883 false == browserSettings.Maximize &&
1884 false == browserSettings.Minimize)
1886 if (
false == browserSettings.WindowSize.IsEmpty)
1890 chromeArguments.Add($
"--window-size={browserSettings.WindowSize.Width},{browserSettings.WindowSize.Height}");
1894 if (0 != browserSettings.WindowSize.X || 0 != browserSettings.WindowSize.Y)
1895 chromeArguments.Add($
"--window-position={browserSettings.WindowSize.X},{browserSettings.WindowSize.Y}");
1897 else if (browserSettings.UseHeadless)
1898 chromeArguments.Add(
"--start-fullscreen");
1900 else if (
true == browserSettings.FullScreen)
1901 chromeArguments.Add(
"--start-fullscreen");
1902 else if (
true == browserSettings.Maximize)
1903 chromeArguments.Add(
"--start-maximized");
1909 chromeArguments.Add(
"--excludeSwitches=enable-automation");
1910 chromeArguments.Add(
"--useAutomationExtension=false");
1914 chromeArguments.Add(
"--no-first-run");
1915 chromeArguments.Add(
"--no-default-browser-check");
1926 chromeArguments.Add(
"--disable-dev-shm-usage");
1929 chromeArguments.Add(
"--disable-infobars");
1930 chromeArguments.Add(
"--disable-session-crashed-bubble");
1931 chromeArguments.Add(
"--hide-crash-restore-bubble");
1932 chromeArguments.Add(
"--noerrdialogs");
1936 string disableFeatures =
"TranslateUI,Translate";
1938 if (BrowserType.Edge == browserSettings.BrowserType)
1939 disableFeatures +=
",msShoppingTrigger,msShopping,msEdgeShoppingUI,msEdgeShoppingList";
1941 chromeArguments.Add($
"--disable-features={disableFeatures}");
1942 chromeArguments.Add(
"--load-media-router-component-extension");
1943 chromeArguments.Add(
"--enable-media-router");
1944 chromeArguments.Add(
"--force-discovery");
1945 chromeArguments.Add(
"--allow-local-network-access");
1947 if (
false ==
string.IsNullOrEmpty(browserSettings.ProfileDataDirectory))
1948 chromeArguments.Add(
$@"--user-data-dir=""{browserSettings.ProfileDataDirectory.Replace("\\
", "/
")}""");
1953 chromeArguments.Add(
"--headless=new");
1954 chromeArguments.Add(
"--disable-notifications");
1955 chromeArguments.Add(
"--disable-gpu");
1956 if (AutomationEngine.PuppeteerPipe == browserSettings.AutomationEngine || AutomationEngine.PuppeteerPipeHW == browserSettings.AutomationEngine)
1957 chromeArguments.Add(
"--no-sandbox");
1960 userAgent =
GetUserAgent(browserSettings, chromeArguments);
1962 userAgent = userAgent?.Trim(
'"');
1964 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Headless [{browserSettings.BrowserType}] using UserAgent [{userAgent}]", browserSettings, GPALObjectType.Other);
1966 chromeArguments.Add($
"--user-agent=\"{userAgent}\"");
1970 if (
false == browserSettings.BlockPopUps)
1971 chromeArguments.Add(
"--disable-popup-blocking");
1975 if (browserSettings.StealthType.HasFlag(StealthType.DarkMode))
1976 chromeArguments.Add(
"--force-dark-mode");
1986 else if (browserSettings.BrowserType == BrowserType.FireFox)
1989 var firefoxArguments =
new List<string>();
1993 firefoxArguments.Add(
"-headless");
1994 firefoxArguments.Add($
"-width={Puppeteer.GetScreenWidth()}");
1995 firefoxArguments.Add($
"-height={Puppeteer.GetScreenHeight()}");
1996 firefoxArguments.Add(
"--disable-notifications");
2006 if (
true ==
string.IsNullOrEmpty(browserSettings.ProfileDataDirectory))
2007 browserSettings.ProfileDataDirectory = FirefoxProfileManager.CreateTempProfileDirectory();
2011 if (
false == browserSettings.TempProfileCreated)
2012 BrowserHelper.RefuseIfProfileIsOpen(browserSettings);
2014 browserSettings.PreviousFirefoxPreferences = FirefoxProfileManager.CreateUserJs(browserSettings.ProfileDataDirectory, browserSettings);
2016 if (
false ==
string.IsNullOrEmpty(browserSettings.ProfileUserName) ||
false ==
string.IsNullOrEmpty(browserSettings.ProfileName))
2017 firefoxArguments.Add($
"-profile \"{(browserSettings.ProfileName ?? browserSettings.ProfileUserName).TrimEnd('\\')}\"");
2018 else if (
false ==
string.IsNullOrEmpty(browserSettings.ProfileDataDirectory))
2019 firefoxArguments.Add($
"-profile \"{browserSettings.ProfileDataDirectory.TrimEnd('\\')}\"");
2021 chromeArguments = firefoxArguments;
2024 bool holdingHandshakeGate =
false;
2025 UdpClient udpHandshake =
null;
2033 if (
null != URL &&
false ==
string.IsNullOrEmpty(URL.Url))
2034 chromeArguments.Add(URL?.Url);
2036 if (AutomationEngine.PuppeteerPipe == browserSettings.AutomationEngine || AutomationEngine.PuppeteerPipeHW == browserSettings.AutomationEngine)
2037 browserSettings.DebugPipe =
true;
2039 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Running workflow for Automation Engine [{browserSettings.AutomationEngine}]",
null, GPALObjectType.None);
2041 if (
true == browserSettings.UseOttoMagic)
2045 try { holdingHandshakeGate = ottoHandshakeGate.WaitOne(OttoHandshakeGateTimeoutMs); }
2046 catch (AbandonedMutexException) { holdingHandshakeGate =
true; }
2048 if (
false == holdingHandshakeGate)
2049 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);
2051 udpHandshake =
new UdpClient(47623);
2052 udpHandshake.Client.ReceiveTimeout = 30000;
2054 KeepFromChildren(udpHandshake);
2057 int firefoxLauncherPid = 0;
2059 if (
true == browserSettings.DebugPipe)
2061 var sa =
new Native.SECURITY_ATTRIBUTES
2063 nLength = Marshal.SizeOf<Native.SECURITY_ATTRIBUTES>(),
2065 lpSecurityDescriptor = IntPtr.Zero
2068 IntPtr hProcess = IntPtr.Zero;
2069 IntPtr reservedPtr = IntPtr.Zero;
2070 AnonymousPipeServerStream stdinPipe =
null, stdoutPipe =
null, stderrPipe =
null;
2075 stdinPipe =
new AnonymousPipeServerStream(PipeDirection.Out, HandleInheritability.Inheritable, 1048576);
2076 stdoutPipe =
new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.Inheritable, 1048576);
2077 stderrPipe =
new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.Inheritable, 1048576);
2080 var chromeCommandLine =
new StringBuilder();
2081 chromeCommandLine.Append(
$@"""{browserPath}""");
2082 chromeCommandLine.Append(
" ");
2083 chromeCommandLine.Append(
string.Join(
" ", chromeArguments));
2086 var startupInfo =
new Native.STARTUPINFO
2088 cb = (uint)Marshal.SizeOf<Native.STARTUPINFO>(),
2091 lpDesktop =
true == browserSettings.HiddenDesktop ? HiddenDesktop.StartupNameFor(browserSettings.HiddenDesktopName) :
null,
2092 dwFlags = Native.STARTF_USESHOWWINDOW | Native.STARTF_USESTDHANDLES,
2093 wShowWindow = (ushort)(headless ? Native.SW_HIDE : Native.SW_SHOWNORMAL),
2094 hStdInput = stdinPipe.ClientSafePipeHandle.DangerousGetHandle(),
2095 hStdOutput = stdoutPipe.ClientSafePipeHandle.DangerousGetHandle(),
2096 hStdError = stderrPipe.ClientSafePipeHandle.DangerousGetHandle(),
2102 created = Native.CreateProcess(
2104 chromeCommandLine.ToString(),
2110 browserSettings.ProfileDataDirectory,
2112 out var processInfo2);
2115 throw new Win32Exception(Marshal.GetLastWin32Error(),
"PuppeteerPipe CreateProcess failed.");
2117 hProcess = processInfo2.hProcess;
2120 browserSettings.InboundPipe = stdinPipe;
2121 browserSettings.OutboundPipe = stdoutPipe;
2122 browserSettings.ErrorPipe = stderrPipe;
2126 var errorReader =
new StreamReader(stderrPipe, Encoding.UTF8,
true, 4096,
false);
2127 _ = Task.Run(async () =>
2131 char[] buffer =
new char[4096];
2133 while ((read = await errorReader.ReadAsync(buffer, 0, buffer.Length)) > 0)
2135 stderr +=
new string(buffer, 0, read);
2137 if (Native.GetExitCodeProcess(hProcess, out uint finalExitCode) && finalExitCode != 0)
2139 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"Chrome exited with code: [{finalExitCode}], Stderr: [{stderr}]", browserSettings, GPALObjectType.Other);
2142 catch (GPALException)
2146 catch (Exception ex)
2148 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Error reading stderr", browserSettings, GPALObjectType.Other, ex);
2152 errorReader.Dispose();
2159 if (!Native.GetExitCodeProcess(hProcess, out uint exitCode))
2160 throw new Win32Exception(Marshal.GetLastWin32Error(),
"Failed to get process exit code.");
2161 if (exitCode != Native.STILL_ACTIVE)
2162 throw new GPALException($
"Chrome exited. Exit code: {exitCode}, Stderr: {stderr}, Check {Path.Combine(browserSettings.ProfileDataDirectory, "chrome_log.txt
")}");
2164 browserSettings.Process = Process.GetProcessById((
int)processInfo2.dwProcessId);
2165 browserSettings.Process.EnableRaisingEvents =
true;
2167 catch (ArgumentException ex)
2169 if (Native.GetExitCodeProcess(hProcess, out uint exitCode) && exitCode != Native.STILL_ACTIVE)
2170 throw new GPALException($
"Chrome exited early. Exit code: {exitCode}, Stderr: {stderr}");
2171 throw new GPALException($
"Failed to get Chrome process by ID: {ex.Message}, Stderr: {stderr}");
2174 catch (GPALException)
2178 catch (Exception ex)
2180 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Failed to launch Chrome with pipes", browserSettings, GPALObjectType.Other, ex);
2185 if (reservedPtr != IntPtr.Zero)
2186 Marshal.FreeHGlobal(reservedPtr);
2187 if (hProcess != IntPtr.Zero)
2188 Native.CloseHandle(hProcess);
2191 stdinPipe?.Dispose();
2192 stdoutPipe?.Dispose();
2193 stderrPipe?.Dispose();
2197 else if (
true == browserSettings.HiddenDesktop)
2203 Native.STARTUPINFO hiddenStartup =
new Native.STARTUPINFO
2205 cb = (uint)Marshal.SizeOf<Native.STARTUPINFO>(),
2206 lpDesktop = HiddenDesktop.StartupNameFor(browserSettings.HiddenDesktopName)
2209 string hiddenCommandLine =
$@"""{browserPath}"" {string.Join(" ", chromeArguments)}";
2211 if (
false == Native.CreateProcess(
null, hiddenCommandLine, IntPtr.Zero, IntPtr.Zero,
false, 0, IntPtr.Zero,
null, ref hiddenStartup, out var hiddenProcess))
2212 throw new Win32Exception(Marshal.GetLastWin32Error(), $
"Could not start [{browserSettings.BrowserType}] on [{browserSettings.HiddenDesktopName}]");
2214 Native.CloseHandle(hiddenProcess.hProcess);
2215 Native.CloseHandle(hiddenProcess.hThread);
2217 browserSettings.Process = Process.GetProcessById((
int)hiddenProcess.dwProcessId);
2220 if (BrowserType.FireFox == browserSettings.BrowserType)
2221 firefoxLauncherPid = browserSettings.Process?.Id ?? 0;
2225 browserSettings.Process = Process.Start(
new ProcessStartInfo
2227 FileName = browserPath,
2228 Arguments =
string.Join(
" ", chromeArguments),
2229 UseShellExecute =
false,
2233 RedirectStandardOutput =
true,
2234 RedirectStandardError =
true
2240 browserSettings.Process.OutputDataReceived += (sender, data) => PublishBrowserOutput(browserSettings, data);
2241 browserSettings.Process.ErrorDataReceived += (sender, data) => PublishBrowserOutput(browserSettings, data);
2242 browserSettings.Process.BeginOutputReadLine();
2243 browserSettings.Process.BeginErrorReadLine();
2246 if (BrowserType.FireFox == browserSettings.BrowserType)
2247 firefoxLauncherPid = browserSettings.Process?.Id ?? 0;
2249 DateTime deadline = DateTime.UtcNow + timeout;
2254 if (
true == headless)
2256 if (
true == browserSettings.Process.HasExited)
2258 string msg = $
"[{browserSettings.BrowserType}] did not start. Exiting.";
2259 GPAL.PublishSimpleEvent(GPALEventType.ERROR, msg);
2260 throw new TimeoutException(msg);
2265 while (browserSettings.Process.MainWindowHandle == IntPtr.Zero && !browserSettings.Process.HasExited)
2267 if (DateTime.UtcNow > deadline)
2269 string msg = $
"[{browserSettings.BrowserType}] did not start in [{secondsToWait}] seconds. Exiting.";
2270 GPAL.PublishSimpleEvent(GPALEventType.ERROR, msg);
2271 throw new TimeoutException(msg);
2281 if (
false == browserSettings.WindowSize.IsEmpty &&
2282 false == browserSettings.FullScreen &&
2283 false == browserSettings.Maximize &&
2284 false == browserSettings.Minimize)
2286 browserSettings.Process.Refresh();
2287 WindowHelper.ResizeWindow(browserSettings.Process, browserSettings.WindowSize);
2292 if (
true == browserSettings.UseOttoMagic)
2296 var remoteEp =
new IPEndPoint(IPAddress.Any, 0);
2299 int launchedPid = browserSettings.Process?.Id ?? 0;
2306 while (0 == restPort)
2312 try { data = udpHandshake.Receive(ref remoteEp); }
2313 catch (SocketException) when (0 != firstHeard)
2315 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"No GPALRestAPI traced back to this browser, so port [{firstHeard}] is being used on the strength of it being the only one announced. An older GPALRestAPI cannot say which browser it belongs to", browserSettings, GPALObjectType.Browser);
2317 restPort = firstHeard;
2321 string[] said = Encoding.UTF8.GetString(data).Trim().Split(
' ');
2326 if (
false ==
int.TryParse(said[0], out
int announced))
2327 throw new InvalidOperationException($
"GPALRestAPI announced [{string.Join(" ", said)}], which is not a port number");
2332 if (2 > said.Length ||
false ==
int.TryParse(said[1], out
int hostPid))
2334 if (0 == firstHeard)
2335 firstHeard = announced;
2340 if (0 == launchedPid ||
true == BrowserHelper.CameFromProcess(hostPid, launchedPid))
2341 restPort = announced;
2344 if (0 == firstHeard)
2345 firstHeard = announced;
2347 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);
2351 browserSettings.RestApiBaseUrl = $
"http://localhost:{restPort}/";
2352 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"GPALRestAPI ready on port [{restPort}].", browserSettings, GPALObjectType.Browser);
2354 catch (GPALException)
2358 catch (Exception ex)
2360 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"GPALRestAPI never announced a port, so this browser cannot reach it", browserSettings, GPALObjectType.Browser, ex);
2365 udpHandshake?.Close();
2369 if (
true == holdingHandshakeGate)
2371 ottoHandshakeGate.ReleaseMutex();
2372 holdingHandshakeGate =
false;
2379 if (firefoxLauncherPid > 0 &&
2380 (browserSettings.Process ==
null || browserSettings.Process.HasExited))
2384 using (var searcher =
new ManagementObjectSearcher(
2385 $
"SELECT ProcessId FROM Win32_Process WHERE ParentProcessId = {firefoxLauncherPid} AND Name = 'firefox.exe'"))
2387 foreach (ManagementObject obj
in searcher.Get())
2389 int childPid = Convert.ToInt32(obj[
"ProcessId"]);
2390 try { browserSettings.Process = System.Diagnostics.Process.GetProcessById(childPid);
break; }
2398 else if (
true == browserSettings.UsePuppeteer)
2400 _ = browserSettings.PuppeteerCommunicator;
2404 if (browserSettings.Maximize)
2408 if (
true == browserSettings.UseOttoMagic)
2410 browser.MagicHelper.Client.Maximize().Execute();
2411 else if (
true == browserSettings.UsePuppeteer)
2412 browserSettings.PuppeteerCommunicator.Maximize().GetAwaiter().GetResult();
2414 catch (GPALException)
2418 catch (Exception ex)
2420 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Failed to maximize browser window", browserSettings, GPALObjectType.Other, ex);
2423 else if (browserSettings.Minimize)
2427 if (
true == browserSettings.UseOttoMagic)
2428 browser.MagicHelper.Client.Minimize().Execute();
2429 else if (
true == browserSettings.UsePuppeteer)
2430 browserSettings.PuppeteerCommunicator.Minimize().GetAwaiter().GetResult();
2432 catch (GPALException)
2436 catch (Exception ex)
2438 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Failed to minimize browser window", browserSettings, GPALObjectType.Other, ex);
2441 else if (browserSettings.FullScreen)
2445 if (
true == browserSettings.UseOttoMagic)
2446 browser.MagicHelper.Client.FullScreen().Execute();
2447 else if (
true == browserSettings.UsePuppeteer)
2448 browserSettings.PuppeteerCommunicator.FullScreen().GetAwaiter().GetResult();
2450 catch (GPALException)
2454 catch (Exception ex)
2456 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Failed to full screen browser window", browserSettings, GPALObjectType.Other, ex);
2462 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"Browser [{browserSettings.BrowserType}] is process [{browserSettings.Process?.Id}] from [{browserPath}] on port [{browserSettings.DebugPort}] at [{browserSettings.PuppeteerUrl}]", browserSettings, GPALObjectType.Other);
2464 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Browser [{browserSettings.BrowserType}] launched.", browserSettings, GPALObjectType.Other);
2466 catch (GPALException)
2470 catch (Exception ex)
2475 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Unable to launch browser [{browserSettings.BrowserType}] for URL [{URL}]", browserSettings, GPALObjectType.Other, ex);
2483 udpHandshake?.Close();
2485 if (
true == holdingHandshakeGate)
2486 ottoHandshakeGate.ReleaseMutex();
2489 return browserSettings.Process;
2545 var endpoints =
new[]
2547 (
"people", 83,
"name"),
2548 (
"planets", 60,
"name"),
2549 (
"films", 6,
"title"),
2550 (
"species", 37,
"name"),
2551 (
"vehicles", 39,
"name"),
2552 (
"starships", 36,
"name")
2555 string FetchQuery(
string endpoint,
int maxId,
string field,
int retries = 3)
2557 for (
int attempt = 0; attempt < retries; attempt++)
2559 int id =
new Random().Next(1, maxId + 1);
2560 var response = restClient.WithEndpoint($
"{endpoint}/{id}").Execute<SwapiResponse>(
false);
2561 if (response !=
null)
2563 return field ==
"name" ? response.name : response.title;
2565 Thread.Sleep(100 * (
int)Math.Pow(2, attempt));
2571 var (endpoint, maxId, field) = endpoints[
new Random().Next(endpoints.Length)];
2572 string query = FetchQuery(endpoint, maxId, field);
2577 var fallbackEndpoint = endpoints[
new Random().Next(endpoints.Length)];
2578 query = FetchQuery(fallbackEndpoint.Item1, fallbackEndpoint.Item2, fallbackEndpoint.Item3);
2581 fallbackEndpoint = endpoints[
new Random().Next(endpoints.Length)];
2582 query = FetchQuery(fallbackEndpoint.Item1, fallbackEndpoint.Item2, fallbackEndpoint.Item3) ??
"star wars";
2586 if (
new Random().NextDouble() > 0.4)
2588 var (otherEndpoint, otherMaxId, otherField) = endpoints[
new Random().Next(endpoints.Length)];
2589 string otherQuery = FetchQuery(otherEndpoint, otherMaxId, otherField);
2590 if (otherQuery !=
null)
2592 query += $
" {otherQuery}";
2596 var fallbackEndpoint = endpoints[
new Random().Next(endpoints.Length)];
2597 otherQuery = FetchQuery(fallbackEndpoint.Item1, fallbackEndpoint.Item2, fallbackEndpoint.Item3);
2598 query += otherQuery !=
null ? $
" {otherQuery}" :
"";
2610 private string GetTargetUrl()
2614 $
"https://www.google.com/search?q={Uri.EscapeDataString(GetRandomStarWarsQuery())}",
2615 $
"https://www.youtube.com/results?search_query={Uri.EscapeDataString(GetRandomStarWarsQuery())}",
2616 $
"https://maps.google.com/?q={Uri.EscapeDataString(GetRandomStarWarsQuery())}",
2617 "https://news.google.com/"
2619 return urls[
new Random().Next(urls.Length)];
2621 private class SwapiResponse
2623 public string name {
get;
set; }
2624 public string title {
get;
set; }
2627 public static string GetFullUrl(
string url, IBrowser browser, out
bool areRobotsAllowed,
bool noRobotsCheck =
false)
2629 List<string> robotsTxt =
new List<string>();
2630 string normalized = url;
2631 areRobotsAllowed =
true;
2636 if (!Uri.TryCreate(url, UriKind.Absolute, out _))
2638 normalized =
"https://" + url;
2642 if (
false == noRobotsCheck &&
true == ((Browser)browser).BrowserSettings.ObeyRobotsTxt &&
false == UrlHelper.IsNonHtmlResource(normalized))
2643 areRobotsAllowed = UrlHelper.CheckRobotsTxt(normalized, browser, robotsTxt);
2645 if (
true == url.StartsWith(
"https://"))
2648 if (
string.IsNullOrWhiteSpace(url) || UrlHelper.AreEquivalent(url,
"https://google.com"))
2649 return "https://google.com";
2652 catch (GPALException)
2656 catch (Exception ex)
2658 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Error getting full url", browser, GPALObjectType.Other, ex);
2674 var attrList = attributes.OrderBy(kvp => kvp.Key)
2675 .Select(kvp => $
"{kvp.Key}={kvp.Value}")
2679 string str =
string.Join(
"|", attrList);
2683 foreach (
char c
in str)
2686 hash = ((hash << 5) - hash + c) | 0;
2702 string chromeVersion;
2704 string firefoxVersion;
2705 string retval =
null;
2707 if (BrowserType.Chrome == browserType)
2709 DriverHelper.GetChromeVersion(out chromeVersion);
2711 if (
null != chromeVersion && 0 < chromeVersion.IndexOf(
'.'))
2712 chromeVersion = chromeVersion.AsSpan().Slice(0, chromeVersion.IndexOf(
'.')).ToString();
2713 retval = String.Format(
GPAL.GPALSettings.ChromeUserAgentTemplate, chromeVersion);
2715 else if (BrowserType.Edge == browserType)
2717 DriverHelper.GetEdgeVersion(out edgeVersion);
2719 if (
null != edgeVersion && 0 < edgeVersion.IndexOf(
'.'))
2720 edgeVersion = edgeVersion.AsSpan().Slice(0, edgeVersion.IndexOf(
'.')).ToString();
2721 retval = String.Format(
GPAL.GPALSettings.EdgeUserAgentTemplate, edgeVersion);
2723 else if (BrowserType.FireFox == browserType)
2725 DriverHelper.GetFirefoxVersion(out firefoxVersion);
2727 if (
null != firefoxVersion && 0 < firefoxVersion.IndexOf(
'.'))
2728 firefoxVersion = firefoxVersion.AsSpan().Slice(0, firefoxVersion.IndexOf(
'.')).ToString();
2729 retval = String.Format(
GPAL.GPALSettings.FirefoxUserAgentTemplate, firefoxVersion);
2743 private static void KeepFromChildren(UdpClient client)
2745 if (
false == Native.SetHandleInformation(client.Client.Handle, Native.HANDLE_FLAG_INHERIT, 0))
2747 $
"Could not take the inherit flag off the handshake socket, so the next OttoMagic launch may find [47623] still held",
2748 null, GPALObjectType.Browser,
new Win32Exception(Marshal.GetLastWin32Error()));
2751 internal string GetUserAgent(BrowserSettings browserSettings, List<string> chromeArguments)
2753 string userAgent =
null;
2754 string browserPath =
null;
2759 if (
false == browserSettings.UserAgentFromBrowser)
2760 return BrowserHelper.ResolveUserAgent(browserSettings);
2762 GPAL.
PublishSimpleEvent(GPALEventType.INFO,
"Getting user agent for headless use.", chromeArguments, GPALObjectType.Other);
2765 switch (browserSettings.BrowserType)
2767 case BrowserType.Chrome:
2768 browserPath = Puppeteer.GetChromePath();
2770 case BrowserType.Edge:
2771 browserPath = Puppeteer.GetEdgePath();
2773 case BrowserType.FireFox:
2774 browserPath = Puppeteer.GetFirefoxPath();
2777 Exception ex =
new Exception(
"Unsupported Browser Type");
2778 GPAL.
PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Unsupported browser type [{browserSettings.BrowserType}]", browser, GPALObjectType.Browser, ex);
2782 UdpClient probeHandshake =
null;
2783 bool holdingProbeGate =
false;
2787 List<string> ourArgs =
new List<string>(chromeArguments);
2790 if (
null == browserSettings.DebugPort)
2791 browserSettings.DebugPort = BrowserHelper.FindFreePort();
2793 if (BrowserType.FireFox != browserSettings.BrowserType)
2794 ourArgs.Add($
"--remote-debugging-port={browserSettings.DebugPort.Value}");
2800 try { holdingProbeGate = ottoHandshakeGate.WaitOne(OttoHandshakeGateTimeoutMs); }
2801 catch (AbandonedMutexException) { holdingProbeGate =
true; }
2803 probeHandshake =
new UdpClient(47623);
2804 probeHandshake.Client.ReceiveTimeout = 30000;
2806 KeepFromChildren(probeHandshake);
2809 browserSettings.Process = Process.Start(
new ProcessStartInfo
2811 FileName = browserPath,
2812 Arguments =
string.Join(
" ", ourArgs),
2813 UseShellExecute =
false
2819 if (BrowserType.FireFox == browserSettings.BrowserType)
2821 var probeEndPoint =
new IPEndPoint(IPAddress.Any, 0);
2822 string probePortText = Encoding.UTF8.GetString(probeHandshake.Receive(ref probeEndPoint)).Trim();
2825 userAgent =
true ==
int.TryParse(probePortText, out
int probePort)
2826 ? ((IRESTClient)GPAL.RESTClient.WithAPIBase($
"http://localhost:{probePort}/").ToGPALObject()).GetUserAgent().Execute()
2827 : Client.GetUserAgent().Execute();
2831 AutomationEngine aeSave = browserSettings.AutomationEngine;
2832 browserSettings.AutomationEngine = AutomationEngine.PuppeteerPort;
2833 _ = browserSettings.PuppeteerCommunicator;
2834 userAgent = browserSettings.PuppeteerClient.GetUserAgent().Execute();
2835 browserSettings.AutomationEngine = aeSave;
2837 userAgent = userAgent.Replace(
"Headless",
"");
2841 var parameters =
new Dictionary<string, object>();
2844 browserSettings.PuppeteerCommunicator._readerReadyTcs?.SetCanceled();
2845 if (
false == browserSettings.PuppeteerCommunicator._usePipes)
2846 browserSettings.PuppeteerCommunicator.CloseOutputWebSocketAsync().GetAwaiter();
2857 var psi =
new ProcessStartInfo
2859 FileName =
"taskkill",
2860 Arguments = $
"/PID {browserSettings.Process.Id} /F /T",
2861 CreateNoWindow =
true,
2862 UseShellExecute =
false
2864 Process.Start(psi).WaitForExit();
2868 Thread.Sleep(1_000);
2870 if (
null != browserSettings.Process &&
false == browserSettings.Process.HasExited)
2874 browserSettings.Process.Kill();
2875 browserSettings.Process.Close();
2880 Thread.Sleep(1_000);
2884 browserSettings.PuppeteerClient =
null;
2885 browserSettings.PuppeteerCommunicator =
null;
2886 browserSettings.Process =
null;
2889 catch (GPALException)
2893 catch (Exception ex)
2896 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Failed to fetch user-agent using [{userAgent}].", browserSettings, GPALObjectType.Other, ex);
2900 probeHandshake?.Close();
2903 if (
true == holdingProbeGate)
2904 ottoHandshakeGate.ReleaseMutex();
2907 return false ==
string.IsNullOrEmpty(userAgent) ? userAgent :
GetUserAgentString(browserSettings.BrowserType);