DXR is a code search and navigation tool aimed at making sense of large projects. It supports full-text and regex searches as well as structural queries.

Header

Mercurial (c68fe15a81fc)

VCS Links

Line Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "DocumentLoadListener.h"

#include "mozilla/AntiTrackingUtils.h"
#include "mozilla/LoadInfo.h"
#include "mozilla/MozPromiseInlines.h"  // For MozPromise::FromDomPromise
#include "mozilla/StaticPrefs_extensions.h"
#include "mozilla/StaticPrefs_fission.h"
#include "mozilla/StaticPrefs_security.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/dom/ClientChannelHelper.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/ContentProcessManager.h"
#include "mozilla/dom/SessionHistoryEntry.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/dom/ipc/IdType.h"
#include "mozilla/net/CookieJarSettings.h"
#include "mozilla/net/HttpChannelParent.h"
#include "mozilla/net/RedirectChannelRegistrar.h"
#include "nsContentSecurityUtils.h"
#include "nsDocShell.h"
#include "nsDocShellLoadState.h"
#include "nsDocShellLoadTypes.h"
#include "nsExternalHelperAppService.h"
#include "nsHttpChannel.h"
#include "nsIBrowser.h"
#include "nsIE10SUtils.h"
#include "nsIHttpChannelInternal.h"
#include "nsIStreamConverterService.h"
#include "nsIViewSourceChannel.h"
#include "nsImportModule.h"
#include "nsMimeTypes.h"
#include "nsRedirectHistoryEntry.h"
#include "nsSandboxFlags.h"
#include "nsURILoader.h"
#include "nsWebNavigationInfo.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/RemoteWebProgress.h"
#include "mozilla/dom/RemoteWebProgressRequest.h"

#ifdef ANDROID
#  include "mozilla/widget/nsWindow.h"
#endif /* ANDROID */

mozilla::LazyLogModule gDocumentChannelLog("DocumentChannel");
#define LOG(fmt) MOZ_LOG(gDocumentChannelLog, mozilla::LogLevel::Verbose, fmt)

using namespace mozilla::dom;

namespace mozilla {
namespace net {

static void SetNeedToAddURIVisit(nsIChannel* aChannel,
                                 bool aNeedToAddURIVisit) {
  nsCOMPtr<nsIWritablePropertyBag2> props(do_QueryInterface(aChannel));
  if (!props) {
    return;
  }

  props->SetPropertyAsBool(NS_LITERAL_STRING("docshell.needToAddURIVisit"),
                           aNeedToAddURIVisit);
}

/**
 * An extension to nsDocumentOpenInfo that we run in the parent process, so
 * that we can make the decision to retarget to content handlers or the external
 * helper app, before we make process switching decisions.
 *
 * This modifies the behaviour of nsDocumentOpenInfo so that it can do
 * retargeting, but doesn't do stream conversion (but confirms that we will be
 * able to do so later).
 *
 * We still run nsDocumentOpenInfo in the content process, but disable
 * retargeting, so that it can only apply stream conversion, and then send data
 * to the docshell.
 */
class ParentProcessDocumentOpenInfo final : public nsDocumentOpenInfo,
                                            public nsIMultiPartChannelListener {
 public:
  ParentProcessDocumentOpenInfo(ParentChannelListener* aListener,
                                uint32_t aFlags,
                                mozilla::dom::BrowsingContext* aBrowsingContext)
      : nsDocumentOpenInfo(aFlags, false),
        mBrowsingContext(aBrowsingContext),
        mListener(aListener) {
    LOG(("ParentProcessDocumentOpenInfo ctor [this=%p]", this));
  }

  NS_DECL_ISUPPORTS_INHERITED

  // The default content listener is always a docshell, so this manually
  // implements the same checks, and if it succeeds, uses the parent
  // channel listener so that we forward onto DocumentLoadListener.
  bool TryDefaultContentListener(nsIChannel* aChannel,
                                 const nsCString& aContentType) {
    uint32_t canHandle = nsWebNavigationInfo::IsTypeSupported(
        aContentType, mBrowsingContext->GetAllowPlugins());
    if (canHandle != nsIWebNavigationInfo::UNSUPPORTED) {
      m_targetStreamListener = mListener;
      nsLoadFlags loadFlags = 0;
      aChannel->GetLoadFlags(&loadFlags);
      aChannel->SetLoadFlags(loadFlags | nsIChannel::LOAD_TARGETED);
      return true;
    }
    return false;
  }

  bool TryDefaultContentListener(nsIChannel* aChannel) override {
    return TryDefaultContentListener(aChannel, mContentType);
  }

  // Generally we only support stream converters that can tell
  // use exactly what type they'll output. If we find one, then
  // we just target to our default listener directly (without
  // conversion), and the content process nsDocumentOpenInfo will
  // run and do the actual conversion.
  nsresult TryStreamConversion(nsIChannel* aChannel) override {
    // The one exception is nsUnknownDecoder, which works in the parent
    // (and we need to know what the content type is before we can
    // decide if it will be handled in the parent), so we run that here.
    if (mContentType.LowerCaseEqualsASCII(UNKNOWN_CONTENT_TYPE)) {
      return nsDocumentOpenInfo::TryStreamConversion(aChannel);
    }

    nsresult rv;
    nsCOMPtr<nsIStreamConverterService> streamConvService =
        do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
    nsAutoCString str;
    rv = streamConvService->ConvertedType(mContentType, aChannel, str);
    NS_ENSURE_SUCCESS(rv, rv);

    // We only support passing data to the default content listener
    // (docshell), and we don't supported chaining converters.
    if (TryDefaultContentListener(aChannel, str)) {
      mContentType = str;
      return NS_OK;
    }
    // This is the same result as nsStreamConverterService uses when it
    // can't find a converter
    return NS_ERROR_FAILURE;
  }

  nsresult TryExternalHelperApp(nsIExternalHelperAppService* aHelperAppService,
                                nsIChannel* aChannel) override {
    RefPtr<nsExternalAppHandler> handler;
    nsresult rv = aHelperAppService->CreateListener(
        mContentType, aChannel, mBrowsingContext, false, nullptr,
        getter_AddRefs(handler));
    if (NS_SUCCEEDED(rv)) {
      m_targetStreamListener = handler;
    }
    return rv;
  }

  nsDocumentOpenInfo* Clone() override {
    mCloned = true;
    return new ParentProcessDocumentOpenInfo(mListener, mFlags,
                                             mBrowsingContext);
  }

  NS_IMETHOD OnStartRequest(nsIRequest* request) override {
    LOG(("ParentProcessDocumentOpenInfo OnStartRequest [this=%p]", this));

    nsresult rv = nsDocumentOpenInfo::OnStartRequest(request);

    // If we didn't find a content handler,
    // and we don't have a listener, then just forward to our
    // default listener. This happens when the channel is in
    // an error state, and we want to just forward that on to be
    // handled in the content process.
    if (!mUsedContentHandler && !m_targetStreamListener) {
      m_targetStreamListener = mListener;
      return m_targetStreamListener->OnStartRequest(request);
    }
    if (m_targetStreamListener != mListener) {
      LOG(
          ("ParentProcessDocumentOpenInfo targeted to non-default listener "
           "[this=%p]",
           this));
      // If this is the only part, then we can immediately tell our listener
      // that it won't be getting any content and disconnect it. For multipart
      // channels we have to wait until we've handled all parts before we know.
      // This does mean that the content process can still Cancel() a multipart
      // response while the response is being handled externally, but this
      // matches the single-process behaviour.
      // If we got cloned, then we don't need to do this, as only the last link
      // needs to do it.
      // Multi-part channels are guaranteed to call OnAfterLastPart, which we
      // forward to the listeners, so it will handle disconnection at that
      // point.
      nsCOMPtr<nsIMultiPartChannel> multiPartChannel =
          do_QueryInterface(request);
      if (!multiPartChannel && !mCloned) {
        DisconnectChildListeners();
      }
    }
    return rv;
  }

  NS_IMETHOD OnAfterLastPart(nsresult aStatus) override {
    mListener->OnAfterLastPart(aStatus);
    return NS_OK;
  }

 private:
  virtual ~ParentProcessDocumentOpenInfo() {
    LOG(("ParentProcessDocumentOpenInfo dtor [this=%p]", this));
  }

  void DisconnectChildListeners() {
    // Tell the DocumentLoadListener to notify the content process that it's
    // been entirely retargeted, and to stop waiting.
    // Clear mListener's pointer to the DocumentLoadListener to break the
    // reference cycle.
    RefPtr<DocumentLoadListener> doc = do_GetInterface(ToSupports(mListener));
    MOZ_ASSERT(doc);
    doc->DisconnectListeners(NS_BINDING_RETARGETED, NS_OK);
    mListener->SetListenerAfterRedirect(nullptr);
  }

  RefPtr<mozilla::dom::BrowsingContext> mBrowsingContext;
  RefPtr<ParentChannelListener> mListener;

  /**
   * Set to true if we got cloned to create a chained listener.
   */
  bool mCloned = false;
};

NS_IMPL_ADDREF_INHERITED(ParentProcessDocumentOpenInfo, nsDocumentOpenInfo)
NS_IMPL_RELEASE_INHERITED(ParentProcessDocumentOpenInfo, nsDocumentOpenInfo)

NS_INTERFACE_MAP_BEGIN(ParentProcessDocumentOpenInfo)
  NS_INTERFACE_MAP_ENTRY(nsIMultiPartChannelListener)
NS_INTERFACE_MAP_END_INHERITING(nsDocumentOpenInfo)

NS_IMPL_ADDREF(DocumentLoadListener)
NS_IMPL_RELEASE(DocumentLoadListener)

NS_INTERFACE_MAP_BEGIN(DocumentLoadListener)
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInterfaceRequestor)
  NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
  NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
  NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
  NS_INTERFACE_MAP_ENTRY(nsIParentChannel)
  NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectReadyCallback)
  NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
  NS_INTERFACE_MAP_ENTRY(nsIMultiPartChannelListener)
  NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink)
  NS_INTERFACE_MAP_ENTRY_CONCRETE(DocumentLoadListener)
NS_INTERFACE_MAP_END

DocumentLoadListener::DocumentLoadListener(
    CanonicalBrowsingContext* aBrowsingContext) {
  LOG(("DocumentLoadListener ctor [this=%p]", this));
  mParentChannelListener = new ParentChannelListener(
      this, aBrowsingContext, aBrowsingContext->UsePrivateBrowsing());
}

DocumentLoadListener::~DocumentLoadListener() {
  LOG(("DocumentLoadListener dtor [this=%p]", this));
}

void DocumentLoadListener::AddURIVisit(nsIChannel* aChannel,
                                       uint32_t aLoadFlags) {
  if (mLoadStateLoadType == LOAD_ERROR_PAGE ||
      mLoadStateLoadType == LOAD_BYPASS_HISTORY) {
    return;
  }

  nsCOMPtr<nsIURI> uri;
  NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));

  nsCOMPtr<nsIURI> previousURI;
  uint32_t previousFlags = 0;
  if (mLoadStateLoadType & nsIDocShell::LOAD_CMD_RELOAD) {
    previousURI = uri;
  } else {
    nsDocShell::ExtractLastVisit(aChannel, getter_AddRefs(previousURI),
                                 &previousFlags);
  }

  // Get the HTTP response code, if available.
  uint32_t responseStatus = 0;
  nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
  if (httpChannel) {
    Unused << httpChannel->GetResponseStatus(&responseStatus);
  }

  RefPtr<CanonicalBrowsingContext> browsingContext =
      mParentChannelListener->GetBrowsingContext();
  nsCOMPtr<nsIWidget> widget =
      browsingContext->GetParentProcessWidgetContaining();

  nsDocShell::InternalAddURIVisit(uri, previousURI, previousFlags,
                                  responseStatus, browsingContext, widget,
                                  mLoadStateLoadType);
}

already_AddRefed<LoadInfo> DocumentLoadListener::CreateLoadInfo(
    CanonicalBrowsingContext* aBrowsingContext, nsDocShellLoadState* aLoadState,
    uint64_t aOuterWindowId) {
  // TODO: Block copied from nsDocShell::DoURILoad, refactor out somewhere
  bool inheritPrincipal = false;

  if (aLoadState->PrincipalToInherit()) {
    bool isSrcdoc =
        aLoadState->HasLoadFlags(nsDocShell::INTERNAL_LOAD_FLAGS_IS_SRCDOC);
    bool inheritAttrs = nsContentUtils::ChannelShouldInheritPrincipal(
        aLoadState->PrincipalToInherit(), aLoadState->URI(),
        true,  // aInheritForAboutBlank
        isSrcdoc);

    bool isURIUniqueOrigin =
        StaticPrefs::security_data_uri_unique_opaque_origin() &&
        SchemeIsData(aLoadState->URI());
    inheritPrincipal = inheritAttrs && !isURIUniqueOrigin;
  }

  nsSecurityFlags securityFlags =
      nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL;
  uint32_t sandboxFlags = aBrowsingContext->GetSandboxFlags();

  if (aLoadState->LoadType() == LOAD_ERROR_PAGE) {
    securityFlags |= nsILoadInfo::SEC_LOAD_ERROR_PAGE;
  }

  if (inheritPrincipal) {
    securityFlags |= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
  }

  RefPtr<LoadInfo> loadInfo;
  if (aBrowsingContext->GetParent()) {
    // Build LoadInfo for TYPE_SUBDOCUMENT
    loadInfo = new LoadInfo(aBrowsingContext, aLoadState->TriggeringPrincipal(),
                            aOuterWindowId, securityFlags, sandboxFlags);
  } else {
    // Build LoadInfo for TYPE_DOCUMENT
    OriginAttributes attrs;
    aBrowsingContext->GetOriginAttributes(attrs);
    loadInfo = new LoadInfo(aBrowsingContext, aLoadState->TriggeringPrincipal(),
                            attrs, aOuterWindowId, securityFlags, sandboxFlags);
  }

  loadInfo->SetHasValidUserGestureActivation(
      aLoadState->HasValidUserGestureActivation());

  return loadInfo.forget();
}

CanonicalBrowsingContext* DocumentLoadListener::GetBrowsingContext() {
  if (!mParentChannelListener) {
    return nullptr;
  }
  return mParentChannelListener->GetBrowsingContext();
}

auto DocumentLoadListener::Open(
    nsDocShellLoadState* aLoadState, uint32_t aCacheKey,
    const Maybe<uint64_t>& aChannelId, const TimeStamp& aAsyncOpenTime,
    nsDOMNavigationTiming* aTiming, Maybe<ClientInfo>&& aInfo,
    uint64_t aOuterWindowId, bool aHasGesture, Maybe<bool> aUriModified,
    Maybe<bool> aIsXFOError, base::ProcessId aPid, nsresult* aRv)
    -> RefPtr<OpenPromise> {
  LOG(("DocumentLoadListener Open [this=%p, uri=%s]", this,
       aLoadState->URI()->GetSpecOrDefault().get()));
  RefPtr<CanonicalBrowsingContext> browsingContext =
      mParentChannelListener->GetBrowsingContext();

  OriginAttributes attrs;
  browsingContext->GetOriginAttributes(attrs);

  MOZ_DIAGNOSTIC_ASSERT_IF(browsingContext->GetParent(),
                           browsingContext->GetParentWindowContext());

  // If this is a top-level load, then rebuild the LoadInfo from scratch,
  // since the goal is to be able to initiate loads in the parent, where the
  // content process won't have provided us with an existing one.
  RefPtr<LoadInfo> loadInfo =
      CreateLoadInfo(browsingContext, aLoadState, aOuterWindowId);

  nsLoadFlags loadFlags = aLoadState->CalculateChannelLoadFlags(
      browsingContext, std::move(aUriModified), std::move(aIsXFOError));

  if (!nsDocShell::CreateAndConfigureRealChannelForLoadState(
          browsingContext, aLoadState, loadInfo, mParentChannelListener,
          nullptr, attrs, loadFlags, aCacheKey, *aRv,
          getter_AddRefs(mChannel))) {
    mParentChannelListener = nullptr;
    return nullptr;
  }

  nsCOMPtr<nsIURI> uriBeingLoaded =
      AntiTrackingUtils::MaybeGetDocumentURIBeingLoaded(mChannel);

  RefPtr<HttpBaseChannel> httpBaseChannel = do_QueryObject(mChannel, aRv);
  if (httpBaseChannel) {
    nsCOMPtr<nsIURI> topWindowURI;
    if (browsingContext->IsTop()) {
      // If this is for the top level loading, the top window URI should be the
      // URI which we are loading.
      topWindowURI = uriBeingLoaded;
    } else if (RefPtr<WindowGlobalParent> topWindow = AntiTrackingUtils::
                   GetTopWindowExcludingExtensionAccessibleContentFrames(
                       browsingContext, uriBeingLoaded)) {
      nsCOMPtr<nsIPrincipal> topWindowPrincipal =
          topWindow->DocumentPrincipal();
      if (topWindowPrincipal && !topWindowPrincipal->GetIsNullPrincipal()) {
        auto* basePrin = BasePrincipal::Cast(topWindowPrincipal);
        basePrin->GetURI(getter_AddRefs(topWindowURI));
      }
    }
    httpBaseChannel->SetTopWindowURI(topWindowURI);
  }

  nsCOMPtr<nsIIdentChannel> identChannel = do_QueryInterface(mChannel);
  if (identChannel && aChannelId) {
    Unused << identChannel->SetChannelId(*aChannelId);
  }

  RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel);
  if (httpChannelImpl) {
    httpChannelImpl->SetWarningReporter(this);
  }

  nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(mChannel);
  if (timedChannel) {
    timedChannel->SetAsyncOpen(aAsyncOpenTime);
  }

  if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel)) {
    Unused << httpChannel->SetRequestContextID(
        browsingContext->GetRequestContextId());
  }

  // nsViewSourceChannel normally replaces the nsIRequest passed to
  // OnStart/StopRequest with itself. We don't need this, and instead
  // we want the original request so that we get different ones for
  // each part of a multipart channel.
  nsCOMPtr<nsIViewSourceChannel> viewSourceChannel;
  if (aPid && (viewSourceChannel = do_QueryInterface(mChannel))) {
    viewSourceChannel->SetReplaceRequest(false);
  }

  // Setup a ClientChannelHelper to watch for redirects, and copy
  // across any serviceworker related data between channels as needed.
  AddClientChannelHelperInParent(mChannel, std::move(aInfo));

  // Recalculate the openFlags, matching the logic in use in Content process.
  // NOTE: The only case not handled here to mirror Content process is
  // redirecting to re-use the channel.
  MOZ_ASSERT(!aLoadState->GetPendingRedirectedChannel());
  uint32_t openFlags = nsDocShell::ComputeURILoaderFlags(
      browsingContext, aLoadState->LoadType());

  RefPtr<ParentProcessDocumentOpenInfo> openInfo =
      new ParentProcessDocumentOpenInfo(mParentChannelListener, openFlags,
                                        browsingContext);
  openInfo->Prepare();

#ifdef ANDROID
  RefPtr<MozPromise<bool, bool, false>> promise;
  if (aLoadState->LoadType() != LOAD_ERROR_PAGE &&
      !(aLoadState->HasLoadFlags(
          nsDocShell::INTERNAL_LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE)) &&
      !(aLoadState->LoadType() & LOAD_HISTORY)) {
    nsCOMPtr<nsIWidget> widget =
        browsingContext->GetParentProcessWidgetContaining();
    RefPtr<nsWindow> window = nsWindow::From(widget);

    if (window) {
      promise = window->OnLoadRequest(
          aLoadState->URI(), nsIBrowserDOMWindow::OPEN_CURRENTWINDOW,
          aLoadState->LoadFlags(), aLoadState->TriggeringPrincipal(),
          aHasGesture, browsingContext->IsTopContent());
    }
  }

  if (promise) {
    RefPtr<DocumentLoadListener> self = this;
    promise->Then(
        GetCurrentThreadSerialEventTarget(), __func__,
        [=](const MozPromise<bool, bool, false>::ResolveOrRejectValue& aValue) {
          if (aValue.IsResolve()) {
            bool handled = aValue.ResolveValue();
            if (handled) {
              self->DisconnectListeners(NS_ERROR_ABORT, NS_ERROR_ABORT);
              mParentChannelListener = nullptr;
            } else {
              nsresult rv = mChannel->AsyncOpen(openInfo);
              if (NS_FAILED(rv)) {
                self->DisconnectListeners(rv, rv);
                mParentChannelListener = nullptr;
              }
            }
          }
        });
  } else
#endif /* ANDROID */
  {
    *aRv = mChannel->AsyncOpen(openInfo);
    if (NS_FAILED(*aRv)) {
      mParentChannelListener = nullptr;
      return nullptr;
    }
  }

  mOtherPid = aPid;
  mChannelCreationURI = aLoadState->URI();
  mLoadStateLoadFlags = aLoadState->LoadFlags();
  mLoadStateLoadType = aLoadState->LoadType();
  mTiming = aTiming;
  mSrcdocData = aLoadState->SrcdocData();
  mBaseURI = aLoadState->BaseURI();
  if (StaticPrefs::fission_sessionHistoryInParent() &&
      browsingContext->GetSessionHistory()) {
    mSessionHistoryInfo =
        browsingContext->CreateSessionHistoryEntryForLoad(aLoadState, mChannel);
  }

  if (auto* ctx = GetBrowsingContext()) {
    ctx->StartDocumentLoad(this);
  }

  *aRv = NS_OK;
  mOpenPromise = new OpenPromise::Private(__func__);
  // We make the promise use direct task dispatch in order to reduce the number
  // of event loops iterations.
  mOpenPromise->UseDirectTaskDispatch(__func__);
  return mOpenPromise;
}

/* static */
bool DocumentLoadListener::OpenFromParent(
    dom::CanonicalBrowsingContext* aBrowsingContext,
    nsDocShellLoadState* aLoadState, uint64_t aOuterWindowId,
    uint32_t* aOutIdent) {
  LOG(("DocumentLoadListener::OpenFromParent"));

  // We currently only support passing nullptr for aLoadInfo for
  // top level browsing contexts.
  if (!aBrowsingContext->IsTopContent() ||
      !aBrowsingContext->GetContentParent()) {
    LOG(("DocumentLoadListener::OpenFromParent failed because of subdoc"));
    return false;
  }

  if (nsCOMPtr<nsIContentSecurityPolicy> csp = aLoadState->Csp()) {
    // Check CSP navigate-to
    bool allowsNavigateTo = false;
    nsresult rv = csp->GetAllowsNavigateTo(aLoadState->URI(),
                                           aLoadState->IsFormSubmission(),
                                           false, /* aWasRedirected */
                                           false, /* aEnforceWhitelist */
                                           &allowsNavigateTo);
    if (NS_FAILED(rv) || !allowsNavigateTo) {
      return false;
    }
  }

  // Any sort of reload/history load would need the cacheKey, and session
  // history data for load flags. We don't have those available in the parent
  // yet, so don't support these load types.
  auto loadType = aLoadState->LoadType();
  if (loadType == LOAD_HISTORY || loadType == LOAD_RELOAD_NORMAL ||
      loadType == LOAD_RELOAD_CHARSET_CHANGE ||
      loadType == LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE ||
      loadType == LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE) {
    LOG(
        ("DocumentLoadListener::OpenFromParent failed because of history "
         "load"));
    return false;
  }

  // Clone because this mutates the load flags in the load state, which
  // breaks nsDocShells expectations of being able to do it.
  RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(*aLoadState);
  loadState->CalculateLoadURIFlags();

  RefPtr<nsDOMNavigationTiming> timing = new nsDOMNavigationTiming(nullptr);
  timing->NotifyNavigationStart(
      aBrowsingContext->GetIsActive()
          ? nsDOMNavigationTiming::DocShellState::eActive
          : nsDOMNavigationTiming::DocShellState::eInactive);

  // We're not a history load or a reload, so we don't need this.
  uint32_t cacheKey = 0;

  // Loads start in the content process might have exposed a channel id to
  // observers, so we need to preserve the value in the parent. That can't have
  // happened here, so Nothing() is fine.
  Maybe<uint64_t> channelId = Nothing();

  // Initial client info is only relevant for subdocument loads, which we're
  // not supporting yet.
  Maybe<dom::ClientInfo> initialClientInfo;

  RefPtr<DocumentLoadListener> listener =
      new DocumentLoadListener(aBrowsingContext);

  nsresult rv;
  auto promise = listener->Open(
      loadState, cacheKey, channelId, TimeStamp::Now(), timing,
      std::move(initialClientInfo), aOuterWindowId, false, Nothing(), Nothing(),
      aBrowsingContext->GetContentParent()->OtherPid(), &rv);
  if (promise) {
    MOZ_ASSERT(NS_SUCCEEDED(rv));
    // Create an entry in the redirect channel registrar to
    // allocate an identifier for this load.
    nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
        RedirectChannelRegistrar::GetOrCreate();
    rv = registrar->RegisterChannel(nullptr, aOutIdent);
    MOZ_ASSERT(NS_SUCCEEDED(rv));
    // Register listener (as an nsIParentChannel) under our new identifier.
    rv = registrar->LinkChannels(*aOutIdent, listener, nullptr);
    MOZ_ASSERT(NS_SUCCEEDED(rv));
  }
  return !!promise;
}

void DocumentLoadListener::CleanupParentLoadAttempt(uint32_t aLoadIdent) {
  nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
      RedirectChannelRegistrar::GetOrCreate();

  nsCOMPtr<nsIParentChannel> parentChannel;
  registrar->GetParentChannel(aLoadIdent, getter_AddRefs(parentChannel));
  RefPtr<DocumentLoadListener> loadListener = do_QueryObject(parentChannel);

  if (loadListener) {
    // If the load listener is still registered, then we must have failed
    // to connect DocumentChannel into it. Better cancel it!
    loadListener->NotifyDocumentChannelFailed();
  }

  registrar->DeregisterChannels(aLoadIdent);
}

auto DocumentLoadListener::ClaimParentLoad(DocumentLoadListener** aListener,
                                           uint32_t aLoadIdent)
    -> RefPtr<OpenPromise> {
  nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
      RedirectChannelRegistrar::GetOrCreate();

  nsCOMPtr<nsIParentChannel> parentChannel;
  registrar->GetParentChannel(aLoadIdent, getter_AddRefs(parentChannel));
  RefPtr<DocumentLoadListener> loadListener = do_QueryObject(parentChannel);
  registrar->DeregisterChannels(aLoadIdent);

  MOZ_ASSERT(loadListener && loadListener->mOpenPromise);
  loadListener.forget(aListener);

  return (*aListener)->mOpenPromise;
}

void DocumentLoadListener::NotifyDocumentChannelFailed() {
  LOG(("DocumentLoadListener NotifyDocumentChannelFailed [this=%p]", this));
  // There's been no calls to ClaimParentLoad, and so no listeners have been
  // attached to mOpenPromise yet. As such we can run Then() on it.
  mOpenPromise->Then(
      GetMainThreadSerialEventTarget(), __func__,
      [](DocumentLoadListener::OpenPromiseSucceededType&& aResolveValue) {
        aResolveValue.mPromise->Resolve(NS_BINDING_ABORTED, __func__);
      },
      []() {});

  Cancel(NS_BINDING_ABORTED);
}

void DocumentLoadListener::Disconnect() {
  LOG(("DocumentLoadListener Disconnect [this=%p]", this));
  // The nsHttpChannel may have a reference to this parent, release it
  // to avoid circular references.
  RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel);
  if (httpChannelImpl) {
    httpChannelImpl->SetWarningReporter(nullptr);
  }

  if (auto* ctx = GetBrowsingContext()) {
    ctx->EndDocumentLoad(this);
  }
}

void DocumentLoadListener::Cancel(const nsresult& aStatusCode) {
  LOG(
      ("DocumentLoadListener Cancel [this=%p, "
       "aStatusCode=%" PRIx32 " ]",
       this, static_cast<uint32_t>(aStatusCode)));
  mCancelled = true;

  if (mDoingProcessSwitch) {
    // If we've already initiated process-switching
    // then we can no longer be cancelled and we'll
    // disconnect the old listeners when done.
    return;
  }

  if (mChannel) {
    mChannel->Cancel(aStatusCode);
  }

  DisconnectListeners(aStatusCode, aStatusCode);
}

void DocumentLoadListener::DisconnectListeners(nsresult aStatus,
                                               nsresult aLoadGroupStatus) {
  LOG(
      ("DocumentLoadListener DisconnectListener [this=%p, "
       "aStatus=%" PRIx32 " aLoadGroupStatus=%" PRIx32 " ]",
       this, static_cast<uint32_t>(aStatus),
       static_cast<uint32_t>(aLoadGroupStatus)));

  RejectOpenPromise(aStatus, aLoadGroupStatus, __func__);

  Disconnect();

  // If we're not going to send anything else to the content process, and
  // we haven't yet consumed a stream filter promise, then we're never going
  // to.
  // TODO: This might be because we retargeted the stream to the download
  // handler or similar. Do we need to attach a stream filter to that?
  mStreamFilterRequests.Clear();
}

void DocumentLoadListener::RedirectToRealChannelFinished(nsresult aRv) {
  LOG(
      ("DocumentLoadListener RedirectToRealChannelFinished [this=%p, "
       "aRv=%" PRIx32 " ]",
       this, static_cast<uint32_t>(aRv)));
  if (NS_FAILED(aRv)) {
    FinishReplacementChannelSetup(aRv);
    return;
  }

  // Wait for background channel ready on target channel
  nsCOMPtr<nsIRedirectChannelRegistrar> redirectReg =
      RedirectChannelRegistrar::GetOrCreate();
  MOZ_ASSERT(redirectReg);

  nsCOMPtr<nsIParentChannel> redirectParentChannel;
  redirectReg->GetParentChannel(mRedirectChannelId,
                                getter_AddRefs(redirectParentChannel));
  if (!redirectParentChannel) {
    FinishReplacementChannelSetup(NS_ERROR_FAILURE);
    return;
  }

  nsCOMPtr<nsIParentRedirectingChannel> redirectingParent =
      do_QueryInterface(redirectParentChannel);
  if (!redirectingParent) {
    // Continue verification procedure if redirecting to non-Http protocol
    FinishReplacementChannelSetup(NS_OK);
    return;
  }

  // Ask redirected channel if verification can proceed.
  // ReadyToVerify will be invoked when redirected channel is ready.
  redirectingParent->ContinueVerification(this);
}

NS_IMETHODIMP
DocumentLoadListener::ReadyToVerify(nsresult aResultCode) {
  FinishReplacementChannelSetup(aResultCode);
  return NS_OK;
}

void DocumentLoadListener::FinishReplacementChannelSetup(nsresult aResult) {
  LOG(
      ("DocumentLoadListener FinishReplacementChannelSetup [this=%p, "
       "aResult=%x]",
       this, int(aResult)));

  if (mDoingProcessSwitch) {
    DisconnectListeners(NS_BINDING_ABORTED, NS_BINDING_ABORTED);
  }

  nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
      RedirectChannelRegistrar::GetOrCreate();
  MOZ_ASSERT(registrar);

  nsCOMPtr<nsIParentChannel> redirectChannel;
  nsresult rv = registrar->GetParentChannel(mRedirectChannelId,
                                            getter_AddRefs(redirectChannel));
  if (NS_FAILED(rv) || !redirectChannel) {
    aResult = NS_ERROR_FAILURE;
  }

  // Release all previously registered channels, they are no longer needed to
  // be kept in the registrar from this moment.
  registrar->DeregisterChannels(mRedirectChannelId);
  mRedirectChannelId = 0;
  if (NS_FAILED(aResult)) {
    if (redirectChannel) {
      redirectChannel->Delete();
    }
    mChannel->Cancel(aResult);
    mChannel->Resume();
    if (auto* ctx = GetBrowsingContext()) {
      ctx->EndDocumentLoad(this);
    }
    return;
  }

  MOZ_ASSERT(
      !SameCOMIdentity(redirectChannel, static_cast<nsIParentChannel*>(this)));

  redirectChannel->SetParentListener(mParentChannelListener);

  ApplyPendingFunctions(redirectChannel);

  if (!ResumeSuspendedChannel(redirectChannel)) {
    nsCOMPtr<nsILoadGroup> loadGroup;
    mChannel->GetLoadGroup(getter_AddRefs(loadGroup));
    if (loadGroup) {
      // We added ourselves to the load group, but attempting
      // to resume has notified us that the channel is already
      // finished. Better remove ourselves from the loadgroup
      // again. The only time the channel will be in a loadgroup
      // is if we're connected to the parent process.
      nsresult status = NS_OK;
      mChannel->GetStatus(&status);
      loadGroup->RemoveRequest(mChannel, nullptr, status);
    }
  }
}

void DocumentLoadListener::ApplyPendingFunctions(
    nsIParentChannel* aChannel) const {
  // We stored the values from all nsIParentChannel functions called since we
  // couldn't handle them. Copy them across to the real channel since it
  // should know what to do.

  nsCOMPtr<nsIParentChannel> parentChannel = aChannel;
  for (auto& variant : mIParentChannelFunctions) {
    variant.match(
        [parentChannel](const nsIHttpChannel::FlashPluginState& aState) {
          parentChannel->NotifyFlashPluginStateChanged(aState);
        },
        [parentChannel](const ClassifierMatchedInfoParams& aParams) {
          parentChannel->SetClassifierMatchedInfo(
              aParams.mList, aParams.mProvider, aParams.mFullHash);
        },
        [parentChannel](const ClassifierMatchedTrackingInfoParams& aParams) {
          parentChannel->SetClassifierMatchedTrackingInfo(aParams.mLists,
                                                          aParams.mFullHashes);
        },
        [parentChannel](const ClassificationFlagsParams& aParams) {
          parentChannel->NotifyClassificationFlags(aParams.mClassificationFlags,
                                                   aParams.mIsThirdParty);
        });
  }

  RefPtr<HttpChannelSecurityWarningReporter> reporter;
  if (RefPtr<HttpChannelParent> httpParent = do_QueryObject(aChannel)) {
    reporter = httpParent;
  } else if (RefPtr<nsHttpChannel> httpChannel = do_QueryObject(aChannel)) {
    reporter = httpChannel->GetWarningReporter();
  }
  if (reporter) {
    for (auto& variant : mSecurityWarningFunctions) {
      variant.match(
          [reporter](const ReportSecurityMessageParams& aParams) {
            Unused << reporter->ReportSecurityMessage(aParams.mMessageTag,
                                                      aParams.mMessageCategory);
          },
          [reporter](const LogBlockedCORSRequestParams& aParams) {
            Unused << reporter->LogBlockedCORSRequest(aParams.mMessage,
                                                      aParams.mCategory);
          },
          [reporter](const LogMimeTypeMismatchParams& aParams) {
            Unused << reporter->LogMimeTypeMismatch(
                aParams.mMessageName, aParams.mWarning, aParams.mURL,
                aParams.mContentType);
          });
    }
  }
}

bool DocumentLoadListener::ResumeSuspendedChannel(
    nsIStreamListener* aListener) {
  LOG(("DocumentLoadListener ResumeSuspendedChannel [this=%p]", this));
  RefPtr<nsHttpChannel> httpChannel = do_QueryObject(mChannel);
  if (httpChannel) {
    httpChannel->SetApplyConversion(mOldApplyConversion);
  }

  if (!mIsFinished) {
    mParentChannelListener->SetListenerAfterRedirect(aListener);
  }

  // If we failed to suspend the channel, then we might have received
  // some messages while the redirected was being handled.
  // Manually send them on now.
  nsTArray<StreamListenerFunction> streamListenerFunctions =
      std::move(mStreamListenerFunctions);
  if (!aListener) {
    streamListenerFunctions.Clear();
  }
  nsresult rv = NS_OK;
  for (auto& variant : streamListenerFunctions) {
    variant.match(
        [&](const OnStartRequestParams& aParams) {
          rv = aListener->OnStartRequest(aParams.request);
          if (NS_FAILED(rv)) {
            aParams.request->Cancel(rv);
          }
        },
        [&](const OnDataAvailableParams& aParams) {
          // Don't deliver OnDataAvailable if we've
          // already failed.
          if (NS_FAILED(rv)) {
            return;
          }
          nsCOMPtr<nsIInputStream> stringStream;
          rv = NS_NewByteInputStream(
              getter_AddRefs(stringStream),
              Span<const char>(aParams.data.get(), aParams.count),
              NS_ASSIGNMENT_DEPEND);
          if (NS_SUCCEEDED(rv)) {
            rv = aListener->OnDataAvailable(aParams.request, stringStream,
                                            aParams.offset, aParams.count);
          }
          if (NS_FAILED(rv)) {
            aParams.request->Cancel(rv);
          }
        },
        [&](const OnStopRequestParams& aParams) {
          if (NS_SUCCEEDED(rv)) {
            aListener->OnStopRequest(aParams.request, aParams.status);
          } else {
            aListener->OnStopRequest(aParams.request, rv);
          }
          rv = NS_OK;
        },
        [&](const OnAfterLastPartParams& aParams) {
          nsCOMPtr<nsIMultiPartChannelListener> multiListener =
              do_QueryInterface(aListener);
          if (multiListener) {
            if (NS_SUCCEEDED(rv)) {
              multiListener->OnAfterLastPart(aParams.status);
            } else {
              multiListener->OnAfterLastPart(rv);
            }
          }
        });
  }
  // We don't expect to get new stream listener functions added
  // via re-entrancy. If this ever happens, we should understand
  // exactly why before allowing it.
  NS_ASSERTION(mStreamListenerFunctions.IsEmpty(),
               "Should not have added new stream listener function!");

  mChannel->Resume();

  if (auto* ctx = GetBrowsingContext()) {
    ctx->EndDocumentLoad(this);
  }

  return !mIsFinished;
}

void DocumentLoadListener::SerializeRedirectData(
    RedirectToRealChannelArgs& aArgs, bool aIsCrossProcess,
    uint32_t aRedirectFlags, uint32_t aLoadFlags,
    ContentParent* aParent) const {
  // Use the original URI of the current channel, as this is what
  // we'll use to construct the channel in the content process.
  aArgs.uri() = mChannelCreationURI;
  if (!aArgs.uri()) {
    mChannel->GetOriginalURI(getter_AddRefs(aArgs.uri()));
  }

  // I previously used HttpBaseChannel::CloneLoadInfoForRedirect, but that
  // clears the principal to inherit, which fails tests (probably because this
  // 'redirect' is usually just an implementation detail). It's also http
  // only, and mChannel can be anything that we redirected to.
  nsCOMPtr<nsILoadInfo> channelLoadInfo;
  mChannel->GetLoadInfo(getter_AddRefs(channelLoadInfo));

  nsCOMPtr<nsIPrincipal> principalToInherit;
  channelLoadInfo->GetPrincipalToInherit(getter_AddRefs(principalToInherit));

  const RefPtr<nsHttpChannel> baseChannel = do_QueryObject(mChannel.get());

  nsCOMPtr<nsILoadContext> loadContext;
  NS_QueryNotificationCallbacks(mChannel, loadContext);
  nsCOMPtr<nsILoadInfo> redirectLoadInfo;

  // Only use CloneLoadInfoForRedirect if we have a load context,
  // since it internally tries to pull OriginAttributes from the
  // the load context and asserts if they don't match the load info.
  // We can end up without a load context if the channel has been aborted
  // and the callbacks have been cleared.
  if (baseChannel && loadContext) {
    redirectLoadInfo = baseChannel->CloneLoadInfoForRedirect(
        aArgs.uri(), nsIChannelEventSink::REDIRECT_INTERNAL);
    redirectLoadInfo->SetResultPrincipalURI(aArgs.uri());

    // The clone process clears this, and then we fail tests..
    // docshell/test/mochitest/test_forceinheritprincipal_overrule_owner.html
    if (principalToInherit) {
      redirectLoadInfo->SetPrincipalToInherit(principalToInherit);
    }
  } else {
    redirectLoadInfo =
        static_cast<mozilla::net::LoadInfo*>(channelLoadInfo.get())->Clone();

    nsCOMPtr<nsIPrincipal> uriPrincipal;
    nsIScriptSecurityManager* sm = nsContentUtils::GetSecurityManager();
    sm->GetChannelURIPrincipal(mChannel, getter_AddRefs(uriPrincipal));

    nsCOMPtr<nsIRedirectHistoryEntry> entry =
        new nsRedirectHistoryEntry(uriPrincipal, nullptr, EmptyCString());

    redirectLoadInfo->AppendRedirectHistoryEntry(entry, true);
  }

  const Maybe<ClientInfo>& reservedClientInfo =
      channelLoadInfo->GetReservedClientInfo();
  if (reservedClientInfo) {
    redirectLoadInfo->SetReservedClientInfo(*reservedClientInfo);
  }

  aArgs.registrarId() = mRedirectChannelId;

  MOZ_ALWAYS_SUCCEEDS(
      ipc::LoadInfoToLoadInfoArgs(redirectLoadInfo, &aArgs.loadInfo()));

  mChannel->GetOriginalURI(getter_AddRefs(aArgs.originalURI()));

  // mChannel can be a nsHttpChannel as well as InterceptedHttpChannel so we
  // can't use baseChannel here.
  if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel)) {
    MOZ_ALWAYS_SUCCEEDS(httpChannel->GetChannelId(&aArgs.channelId()));
  }

  aArgs.redirectMode() = nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW;
  nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal =
      do_QueryInterface(mChannel);
  if (httpChannelInternal) {
    MOZ_ALWAYS_SUCCEEDS(
        httpChannelInternal->GetRedirectMode(&aArgs.redirectMode()));
  }

  if (baseChannel) {
    aArgs.init() =
        Some(baseChannel
                 ->CloneReplacementChannelConfig(
                     true, aRedirectFlags,
                     HttpBaseChannel::ReplacementReason::DocumentChannel)
                 .Serialize(aParent));
  }

  uint32_t contentDispositionTemp;
  nsresult rv = mChannel->GetContentDisposition(&contentDispositionTemp);
  if (NS_SUCCEEDED(rv)) {
    aArgs.contentDisposition() = Some(contentDispositionTemp);
  }

  nsString contentDispositionFilenameTemp;
  rv = mChannel->GetContentDispositionFilename(contentDispositionFilenameTemp);
  if (NS_SUCCEEDED(rv)) {
    aArgs.contentDispositionFilename() = Some(contentDispositionFilenameTemp);
  }

  SetNeedToAddURIVisit(mChannel, false);

  aArgs.newLoadFlags() = aLoadFlags;
  aArgs.redirectFlags() = aRedirectFlags;
  aArgs.redirectIdentifier() = mCrossProcessRedirectIdentifier;
  aArgs.properties() = do_QueryObject(mChannel.get());
  aArgs.srcdocData() = mSrcdocData;
  aArgs.baseUri() = mBaseURI;
  aArgs.loadStateLoadFlags() = mLoadStateLoadFlags;
  aArgs.loadStateLoadType() = mLoadStateLoadType;
  if (mSessionHistoryInfo) {
    aArgs.sessionHistoryInfo().emplace(
        mSessionHistoryInfo->mId, MakeUnique<mozilla::dom::SessionHistoryInfo>(
                                      *mSessionHistoryInfo->mInfo));
  }
}

bool DocumentLoadListener::MaybeTriggerProcessSwitch(
    bool* aWillSwitchToRemote) {
  MOZ_ASSERT(XRE_IsParentProcess());
  MOZ_DIAGNOSTIC_ASSERT(!mDoingProcessSwitch,
                        "Already in the middle of switching?");
  MOZ_DIAGNOSTIC_ASSERT(mChannel);
  MOZ_DIAGNOSTIC_ASSERT(mParentChannelListener);
  MOZ_DIAGNOSTIC_ASSERT(aWillSwitchToRemote);

  LOG(("DocumentLoadListener MaybeTriggerProcessSwitch [this=%p]", this));

  // Get the BrowsingContext which will be switching processes.
  RefPtr<CanonicalBrowsingContext> browsingContext =
      mParentChannelListener->GetBrowsingContext();
  if (NS_WARN_IF(!browsingContext)) {
    LOG(("Process Switch Abort: no browsing context"));
    return false;
  }
  if (!browsingContext->IsContent()) {
    LOG(("Process Switch Abort: non-content browsing context"));
    return false;
  }
  if (browsingContext->GetParent() && !browsingContext->UseRemoteSubframes()) {
    LOG(("Process Switch Abort: remote subframes disabled"));
    return false;
  }

  if (browsingContext->GetParentWindowContext() &&
      browsingContext->GetParentWindowContext()->IsInProcess()) {
    LOG(("Process Switch Abort: Subframe with in-process parent"));
    return false;
  }

  // We currently can't switch processes for toplevel loads unless they're
  // loaded within a browser tab.
  // FIXME: Ideally we won't do this in the future.
  nsCOMPtr<nsIBrowser> browser;
  bool isPreloadSwitch = false;
  if (!browsingContext->GetParent()) {
    Element* browserElement = browsingContext->GetEmbedderElement();
    if (!browserElement) {
      LOG(("Process Switch Abort: cannot get browser element"));
      return false;
    }
    browser = browserElement->AsBrowser();
    if (!browser) {
      LOG(("Process Switch Abort: not loaded within nsIBrowser"));
      return false;
    }
    bool loadedInTab = false;
    if (NS_FAILED(browser->GetCanPerformProcessSwitch(&loadedInTab)) ||
        !loadedInTab) {
      LOG(("Process Switch Abort: browser is not loaded in a tab"));
      return false;
    }

    // Leaving about:newtab from a used to be preloaded browser should run the
    // process selecting algorithm again.
    nsAutoString isPreloadBrowserStr;
    if (browserElement->GetAttr(kNameSpaceID_None, nsGkAtoms::preloadedState,
                                isPreloadBrowserStr)) {
      if (isPreloadBrowserStr.EqualsLiteral("consumed")) {
        nsCOMPtr<nsIURI> originalURI;
        if (NS_SUCCEEDED(
                mChannel->GetOriginalURI(getter_AddRefs(originalURI)))) {
          if (!originalURI->GetSpecOrDefault().EqualsLiteral("about:newtab")) {
            LOG(("Process Switch: leaving preloaded browser"));
            isPreloadSwitch = true;
            browserElement->UnsetAttr(kNameSpaceID_None,
                                      nsGkAtoms::preloadedState, true);
          }
        }
      }
    }
  }

  // Get information about the current document loaded in our BrowsingContext.
  nsCOMPtr<nsIPrincipal> currentPrincipal;
  if (RefPtr<WindowGlobalParent> wgp =
          browsingContext->GetCurrentWindowGlobal()) {
    currentPrincipal = wgp->DocumentPrincipal();
  }
  RefPtr<ContentParent> contentParent = browsingContext->GetContentParent();
  MOZ_ASSERT(!OtherPid() || contentParent,
             "Only PPDC is allowed to not have an existing ContentParent");

  // Get the final principal, used to select which process to load into.
  nsCOMPtr<nsIPrincipal> resultPrincipal;
  nsresult rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
      mChannel, getter_AddRefs(resultPrincipal));
  if (NS_FAILED(rv)) {
    LOG(("Process Switch Abort: failed to get channel result principal"));
    return false;
  }

  // Determine our COOP status, which will be used to determine our preferred
  // remote type.
  bool isCOOPSwitch = HasCrossOriginOpenerPolicyMismatch();
  nsILoadInfo::CrossOriginOpenerPolicy coop =
      nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
  if (!browsingContext->IsTop()) {
    coop = browsingContext->Top()->GetOpenerPolicy();
  } else if (nsCOMPtr<nsIHttpChannelInternal> httpChannel =
                 do_QueryInterface(mChannel)) {
    MOZ_ALWAYS_SUCCEEDS(httpChannel->GetCrossOriginOpenerPolicy(&coop));
  }

  nsAutoString currentRemoteType;
  if (contentParent) {
    currentRemoteType = contentParent->GetRemoteType();
  } else {
    currentRemoteType = VoidString();
  }
  nsAutoString preferredRemoteType = currentRemoteType;
  if (coop ==
      nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP) {
    // We want documents with SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP COOP
    // policy to be loaded in a separate process in which we can enable
    // high-resolution timers.
    nsAutoCString siteOrigin;
    resultPrincipal->GetSiteOrigin(siteOrigin);
    preferredRemoteType.Assign(
        NS_LITERAL_STRING(WITH_COOP_COEP_REMOTE_TYPE_PREFIX));
    preferredRemoteType.Append(NS_ConvertUTF8toUTF16(siteOrigin));
  } else if (isCOOPSwitch) {
    // If we're doing a COOP switch, we do not need any affinity to the current
    // remote type. Clear it back to the default value.
    preferredRemoteType.Assign(NS_LITERAL_STRING(DEFAULT_REMOTE_TYPE));
  }
  MOZ_DIAGNOSTIC_ASSERT(!contentParent || !preferredRemoteType.IsEmpty(),
                        "Unexpected empty remote type!");

  LOG(
      ("DocumentLoadListener GetRemoteTypeForPrincipal "
       "[this=%p, contentParent=%s, preferredRemoteType=%s]",
       this, NS_ConvertUTF16toUTF8(currentRemoteType).get(),
       NS_ConvertUTF16toUTF8(preferredRemoteType).get()));

  nsCOMPtr<nsIE10SUtils> e10sUtils =
      do_ImportModule("resource://gre/modules/E10SUtils.jsm", "E10SUtils");
  if (!e10sUtils) {
    LOG(("Process Switch Abort: Could not import E10SUtils"));
    return false;
  }

  nsAutoString remoteType;
  rv = e10sUtils->GetRemoteTypeForPrincipal(
      resultPrincipal, mChannelCreationURI, browsingContext->UseRemoteTabs(),
      browsingContext->UseRemoteSubframes(), preferredRemoteType,
      currentPrincipal, browsingContext->GetParent(), remoteType);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    LOG(("Process Switch Abort: getRemoteTypeForPrincipal threw an exception"));
    return false;
  }

  LOG(("GetRemoteTypeForPrincipal -> current:%s remoteType:%s",
       NS_ConvertUTF16toUTF8(currentRemoteType).get(),
       NS_ConvertUTF16toUTF8(remoteType).get()));

  // Check if a process switch is needed.
  if (currentRemoteType == remoteType && !isCOOPSwitch && !isPreloadSwitch) {
    LOG(("Process Switch Abort: type (%s) is compatible",
         NS_ConvertUTF16toUTF8(remoteType).get()));
    return false;
  }
  if (NS_WARN_IF(remoteType.IsEmpty())) {
    LOG(("Process Switch Abort: non-remote target process"));
    return false;
  }

  *aWillSwitchToRemote = !remoteType.IsEmpty();

  LOG(("Process Switch: Changing Remoteness from '%s' to '%s'",
       NS_ConvertUTF16toUTF8(currentRemoteType).get(),
       NS_ConvertUTF16toUTF8(remoteType).get()));

  // XXX: This is super hacky, and we should be able to do something better.
  static uint64_t sNextCrossProcessRedirectIdentifier = 0;
  mCrossProcessRedirectIdentifier = ++sNextCrossProcessRedirectIdentifier;
  mDoingProcessSwitch = true;

  RefPtr<DocumentLoadListener> self = this;
  // At this point, we're actually going to perform a process switch, which
  // involves calling into other logic.
  if (browsingContext->GetParent()) {
    LOG(("Process Switch: Calling ChangeFrameRemoteness"));
    // If we're switching a subframe, ask BrowsingContext to do it for us.
    MOZ_ASSERT(!isCOOPSwitch);
    browsingContext
        ->ChangeFrameRemoteness(remoteType, mCrossProcessRedirectIdentifier)
        ->Then(
            GetMainThreadSerialEventTarget(), __func__,
            [self](BrowserParent* aBrowserParent) {
              MOZ_ASSERT(self->mChannel,
                         "Something went wrong, channel got cancelled");
              self->TriggerRedirectToRealChannel(
                  Some(aBrowserParent->Manager()->ChildID()));
            },
            [self](nsresult aStatusCode) {
              MOZ_ASSERT(NS_FAILED(aStatusCode), "Status should be error");
              self->RedirectToRealChannelFinished(aStatusCode);
            });
    return true;
  }

  LOG(("Process Switch: Calling nsIBrowser::PerformProcessSwitch"));
  // We're switching a toplevel BrowsingContext's process. This has to be done
  // using nsIBrowser.
  RefPtr<dom::Promise> domPromise;
  browser->PerformProcessSwitch(remoteType, mCrossProcessRedirectIdentifier,
                                isCOOPSwitch, getter_AddRefs(domPromise));
  MOZ_DIAGNOSTIC_ASSERT(domPromise,
                        "PerformProcessSwitch didn't return a promise");

  MozPromise<uint64_t, nsresult, true>::FromDomPromise(domPromise)
      ->Then(
          GetMainThreadSerialEventTarget(), __func__,
          [self](uint64_t aCpId) {
            MOZ_ASSERT(self->mChannel,
                       "Something went wrong, channel got cancelled");
            self->TriggerRedirectToRealChannel(Some(aCpId));
          },
          [self](nsresult aStatusCode) {
            MOZ_ASSERT(NS_FAILED(aStatusCode), "Status should be error");
            self->RedirectToRealChannelFinished(aStatusCode);
          });
  return true;
}

RefPtr<PDocumentChannelParent::RedirectToRealChannelPromise>
DocumentLoadListener::RedirectToRealChannel(
    uint32_t aRedirectFlags, uint32_t aLoadFlags,
    const Maybe<uint64_t>& aDestinationProcess,
    nsTArray<ParentEndpoint>&& aStreamFilterEndpoints) {
  LOG(
      ("DocumentLoadListener RedirectToRealChannel [this=%p] "
       "aRedirectFlags=%" PRIx32 ", aLoadFlags=%" PRIx32,
       this, aRedirectFlags, aLoadFlags));

  // TODO(djg): Add the last URI visit to history if success. Is there a better
  // place to handle this? Need access to the updated aLoadFlags.
  nsresult status = NS_OK;
  mChannel->GetStatus(&status);
  bool updateGHistory =
      nsDocShell::ShouldUpdateGlobalHistory(mLoadStateLoadType);
  if (NS_SUCCEEDED(status) && updateGHistory && !net::ChannelIsPost(mChannel)) {
    AddURIVisit(mChannel, aLoadFlags);
  }

  // Register the new channel and obtain id for it
  nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
      RedirectChannelRegistrar::GetOrCreate();
  MOZ_ASSERT(registrar);
  MOZ_ALWAYS_SUCCEEDS(
      registrar->RegisterChannel(mChannel, &mRedirectChannelId));

  if (aDestinationProcess) {
    dom::ContentParent* cp =
        dom::ContentProcessManager::GetSingleton()->GetContentProcessById(
            ContentParentId{*aDestinationProcess});
    if (!cp) {
      return PDocumentChannelParent::RedirectToRealChannelPromise::
          CreateAndReject(ipc::ResponseRejectReason::SendError, __func__);
    }

    RedirectToRealChannelArgs args;
    SerializeRedirectData(args, !!aDestinationProcess, aRedirectFlags,
                          aLoadFlags, cp);
    if (mTiming) {
      mTiming->Anonymize(args.uri());
      args.timing() = Some(std::move(mTiming));
    }

    auto loadInfo = args.loadInfo();

    if (loadInfo.isNothing()) {
      return PDocumentChannelParent::RedirectToRealChannelPromise::
          CreateAndReject(ipc::ResponseRejectReason::SendError, __func__);
    }

    auto triggeringPrincipalOrErr =
        PrincipalInfoToPrincipal(loadInfo.ref().triggeringPrincipalInfo());

    if (triggeringPrincipalOrErr.isOk()) {
      nsCOMPtr<nsIPrincipal> triggeringPrincipal =
          triggeringPrincipalOrErr.unwrap();
      cp->TransmitBlobDataIfBlobURL(args.uri(), triggeringPrincipal);
    }

    return cp->SendCrossProcessRedirect(args,
                                        std::move(aStreamFilterEndpoints));
  }

  if (mOpenPromiseResolved) {
    LOG(
        ("DocumentLoadListener RedirectToRealChannel [this=%p] "
         "promise already resolved. Aborting.",
         this));
    // The promise has already been resolved or aborted, so we have no way to
    // return a promise again to the listener which would cancel the operation.
    // Reject the promise immediately.
    return PDocumentChannelParent::RedirectToRealChannelPromise::
        CreateAndResolve(NS_BINDING_ABORTED, __func__);
  }

  // This promise will be passed on the promise listener which will
  // resolve this promise for us.
  auto promise =
      MakeRefPtr<PDocumentChannelParent::RedirectToRealChannelPromise::Private>(
          __func__);
  mOpenPromise->Resolve(
      OpenPromiseSucceededType({std::move(aStreamFilterEndpoints),
                                aRedirectFlags, aLoadFlags, promise}),
      __func__);

  // There is no way we could come back here if the promise had been resolved
  // previously. But for clarity and to avoid all doubt, we set this boolean to
  // true.
  mOpenPromiseResolved = true;

  return promise;
}

void DocumentLoadListener::TriggerRedirectToRealChannel(
    const Maybe<uint64_t>& aDestinationProcess) {
  LOG((
      "DocumentLoadListener::TriggerRedirectToRealChannel [this=%p] "
      "aDestinationProcess=%" PRId64,
      this, aDestinationProcess ? int64_t(*aDestinationProcess) : int64_t(-1)));
  // This initiates replacing the current DocumentChannel with a
  // protocol specific 'real' channel, maybe in a different process than
  // the current DocumentChannelChild, if aDestinationProces is set.
  // It registers the current mChannel with the registrar to get an ID
  // so that the remote end can setup a new IPDL channel and lookup
  // the same underlying channel.
  // We expect this process to finish with FinishReplacementChannelSetup
  // (for both in-process and process switch cases), where we cleanup
  // the registrar and copy across any needed state to the replacing
  // IPDL parent object.

  nsTArray<ParentEndpoint> parentEndpoints(mStreamFilterRequests.Length());
  if (!mStreamFilterRequests.IsEmpty()) {
    base::ProcessId pid = OtherPid();
    if (aDestinationProcess) {
      dom::ContentParent* cp =
          dom::ContentProcessManager::GetSingleton()->GetContentProcessById(
              ContentParentId(*aDestinationProcess));
      if (cp) {
        pid = cp->OtherPid();
      }
    }

    for (StreamFilterRequest& request : mStreamFilterRequests) {
      ParentEndpoint parent;
      nsresult rv = extensions::PStreamFilter::CreateEndpoints(
          pid, request.mChildProcessId, &parent, &request.mChildEndpoint);

      if (NS_FAILED(rv)) {
        request.mPromise->Reject(false, __func__);
        request.mPromise = nullptr;
      } else {
        parentEndpoints.AppendElement(std::move(parent));
      }
    }
  }

  // If we didn't have any redirects, then we pass the REDIRECT_INTERNAL flag
  // for this channel switch so that it isn't recorded in session history etc.
  // If there were redirect(s), then we want this switch to be recorded as a
  // real one, since we have a new URI.
  uint32_t redirectFlags = 0;
  if (!mHaveVisibleRedirect) {
    redirectFlags = nsIChannelEventSink::REDIRECT_INTERNAL;
  }

  uint32_t newLoadFlags = nsIRequest::LOAD_NORMAL;
  MOZ_ALWAYS_SUCCEEDS(mChannel->GetLoadFlags(&newLoadFlags));
  // We're pulling our flags from the inner channel, which may not have this
  // flag set on it. This is the case when loading a 'view-source' channel.
  newLoadFlags |= nsIChannel::LOAD_DOCUMENT_URI;
  if (!aDestinationProcess) {
    newLoadFlags |= nsIChannel::LOAD_REPLACE;
  }

  // INHIBIT_PERSISTENT_CACHING is clearing during http redirects (from
  // both parent and content process channel instances), but only ever
  // re-added to the parent-side nsHttpChannel.
  // To match that behaviour, we want to explicitly avoid copying this flag
  // back to our newly created content side channel, otherwise it can
  // affect sub-resources loads in the same load group.
  nsCOMPtr<nsIURI> uri;
  mChannel->GetURI(getter_AddRefs(uri));
  if (uri && uri->SchemeIs("https")) {
    newLoadFlags &= ~nsIRequest::INHIBIT_PERSISTENT_CACHING;
  }

  RefPtr<DocumentLoadListener> self = this;
  RedirectToRealChannel(redirectFlags, newLoadFlags, aDestinationProcess,
                        std::move(parentEndpoints))
      ->Then(
          GetCurrentThreadSerialEventTarget(), __func__,
          [self, requests = std::move(mStreamFilterRequests)](
              const nsresult& aResponse) mutable {
            for (StreamFilterRequest& request : requests) {
              if (request.mPromise) {
                request.mPromise->Resolve(std::move(request.mChildEndpoint),
                                          __func__);
                request.mPromise = nullptr;
              }
            }
            self->RedirectToRealChannelFinished(aResponse);
          },
          [self](const mozilla::ipc::ResponseRejectReason) {
            self->RedirectToRealChannelFinished(NS_ERROR_FAILURE);
          });
}

NS_IMETHODIMP
DocumentLoadListener::OnStartRequest(nsIRequest* aRequest) {
  LOG(("DocumentLoadListener OnStartRequest [this=%p]", this));

  nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(aRequest);
  if (multiPartChannel) {
    multiPartChannel->GetBaseChannel(getter_AddRefs(mChannel));
  } else {
    mChannel = do_QueryInterface(aRequest);
  }
  MOZ_DIAGNOSTIC_ASSERT(mChannel);

  RefPtr<nsHttpChannel> httpChannel = do_QueryObject(mChannel);

  // Enforce CSP frame-ancestors and x-frame-options checks which
  // might cancel the channel.
  nsContentSecurityUtils::PerformCSPFrameAncestorAndXFOCheck(mChannel);

  // Generally we want to switch to a real channel even if the request failed,
  // since the listener might want to access protocol-specific data (like http
  // response headers) in its error handling.
  // An exception to this is when nsExtProtocolChannel handled the request and
  // returned NS_ERROR_NO_CONTENT, since creating a real one in the content
  // process will attempt to handle the URI a second time.
  nsresult status = NS_OK;
  aRequest->GetStatus(&status);
  if (status == NS_ERROR_NO_CONTENT) {
    DisconnectListeners(status, status);
    return NS_OK;
  }

  mStreamListenerFunctions.AppendElement(StreamListenerFunction{
      VariantIndex<0>{}, OnStartRequestParams{aRequest}});

  if (mOpenPromiseResolved || mInitiatedRedirectToRealChannel) {
    // I we have already resolved the promise, there's no point to continue
    // attempting a process switch or redirecting to the real channel.
    // We can also have multiple calls to OnStartRequest when dealing with
    // multi-part content, but only want to redirect once.
    return NS_OK;
  }

  mChannel->Suspend();

  mInitiatedRedirectToRealChannel = true;

  // Determine if a new process needs to be spawned. If it does, this will
  // trigger a cross process switch, and we should hold off on redirecting to
  // the real channel.
  bool willBeRemote = false;
  if (status == NS_BINDING_ABORTED ||
      !MaybeTriggerProcessSwitch(&willBeRemote)) {
    TriggerRedirectToRealChannel();

    // If we're not switching, then check if we're currently remote.
    if (GetBrowsingContext() && GetBrowsingContext()->GetContentParent()) {
      willBeRemote = true;
    }
  }

  // If we're going to be delivering this channel to a remote content
  // process, then we want to install any required content conversions
  // in the content process.
  // The caller of this OnStartRequest will install a conversion
  // helper after we return if we haven't disabled conversion. Normally
  // HttpChannelParent::OnStartRequest would disable conversion, but we're
  // defering calling that until later. Manually disable it now to prevent the
  // converter from being installed (since we want the child to do it), and
  // also save the value so that when we do call
  // HttpChannelParent::OnStartRequest, we can have the value as it originally
  // was.
  if (httpChannel) {
    Unused << httpChannel->GetApplyConversion(&mOldApplyConversion);
    if (willBeRemote) {
      httpChannel->SetApplyConversion(false);
    }
  }

  return NS_OK;
}

NS_IMETHODIMP
DocumentLoadListener::OnStopRequest(nsIRequest* aRequest,
                                    nsresult aStatusCode) {
  LOG(("DocumentLoadListener OnStopRequest [this=%p]", this));
  mStreamListenerFunctions.AppendElement(StreamListenerFunction{
      VariantIndex<2>{}, OnStopRequestParams{aRequest, aStatusCode}});

  // If we're not a multi-part channel, then we're finished and we don't
  // expect any further events. If we are, then this might be called again,
  // so wait for OnAfterLastPart instead.
  nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(aRequest);
  if (!multiPartChannel) {
    mIsFinished = true;
  }

  mStreamFilterRequests.Clear();

  return NS_OK;
}

NS_IMETHODIMP
DocumentLoadListener::OnDataAvailable(nsIRequest* aRequest,
                                      nsIInputStream* aInputStream,
                                      uint64_t aOffset, uint32_t aCount) {
  LOG(("DocumentLoadListener OnDataAvailable [this=%p]", this));
  // This isn't supposed to happen, since we suspended the channel, but
  // sometimes Suspend just doesn't work. This can happen when we're routing
  // through nsUnknownDecoder to sniff the content type, and it doesn't handle
  // being suspended. Let's just store the data and manually forward it to our
  // redirected channel when it's ready.
  nsCString data;
  nsresult rv = NS_ReadInputStreamToString(aInputStream, data, aCount);
  NS_ENSURE_SUCCESS(rv, rv);

  mStreamListenerFunctions.AppendElement(StreamListenerFunction{
      VariantIndex<1>{},
      OnDataAvailableParams{aRequest, data, aOffset, aCount}});

  return NS_OK;
}

//-----------------------------------------------------------------------------
// DoucmentLoadListener::nsIMultiPartChannelListener
//-----------------------------------------------------------------------------

NS_IMETHODIMP
DocumentLoadListener::OnAfterLastPart(nsresult aStatus) {
  LOG(("DocumentLoadListener OnAfterLastPart [this=%p]", this));
  if (!mInitiatedRedirectToRealChannel) {
    // if we get here, and we haven't initiated a redirect to a real
    // channel, then it means we never got OnStartRequest (maybe a problem?)
    // and we retargeted everything.
    LOG(("DocumentLoadListener Disconnecting child"));
    DisconnectListeners(NS_BINDING_RETARGETED, NS_OK);
    return NS_OK;
  }
  mStreamListenerFunctions.AppendElement(StreamListenerFunction{
      VariantIndex<3>{}, OnAfterLastPartParams{aStatus}});
  mIsFinished = true;
  return NS_OK;
}

NS_IMETHODIMP
DocumentLoadListener::GetInterface(const nsIID& aIID, void** result) {
  RefPtr<CanonicalBrowsingContext> browsingContext =
      mParentChannelListener->GetBrowsingContext();
  if (aIID.Equals(NS_GET_IID(nsILoadContext)) && browsingContext) {
    browsingContext.forget(result);
    return NS_OK;
  }

  return QueryInterface(aIID, result);
}

////////////////////////////////////////////////////////////////////////////////
// nsIParentChannel
////////////////////////////////////////////////////////////////////////////////

NS_IMETHODIMP
DocumentLoadListener::SetParentListener(
    mozilla::net::ParentChannelListener* listener) {
  // We don't need this (do we?)
  return NS_OK;
}

// Rather than forwarding all these nsIParentChannel functions to the child,
// we cache a list of them, and then ask the 'real' channel to forward them
// for us after it's created.
NS_IMETHODIMP
DocumentLoadListener::NotifyFlashPluginStateChanged(
    nsIHttpChannel::FlashPluginState aState) {
  mIParentChannelFunctions.AppendElement(
      IParentChannelFunction{VariantIndex<0>{}, aState});
  return NS_OK;
}

NS_IMETHODIMP
DocumentLoadListener::SetClassifierMatchedInfo(const nsACString& aList,
                                               const nsACString& aProvider,
                                               const nsACString& aFullHash) {
  ClassifierMatchedInfoParams params;
  params.mList = aList;
  params.mProvider = aProvider;
  params.mFullHash = aFullHash;

  mIParentChannelFunctions.AppendElement(
      IParentChannelFunction{VariantIndex<1>{}, std::move(params)});
  return NS_OK;
}

NS_IMETHODIMP
DocumentLoadListener::SetClassifierMatchedTrackingInfo(
    const nsACString& aLists, const nsACString& aFullHash) {
  ClassifierMatchedTrackingInfoParams params;
  params.mLists = aLists;
  params.mFullHashes = aFullHash;

  mIParentChannelFunctions.AppendElement(
      IParentChannelFunction{VariantIndex<2>{}, std::move(params)});
  return NS_OK;
}

NS_IMETHODIMP
DocumentLoadListener::NotifyClassificationFlags(uint32_t aClassificationFlags,
                                                bool aIsThirdParty) {
  mIParentChannelFunctions.AppendElement(IParentChannelFunction{
      VariantIndex<3>{},
      ClassificationFlagsParams{aClassificationFlags, aIsThirdParty}});
  return NS_OK;
}

NS_IMETHODIMP
DocumentLoadListener::Delete() {
  MOZ_ASSERT_UNREACHABLE("This method is unused");
  return NS_OK;
}

////////////////////////////////////////////////////////////////////////////////
// nsIChannelEventSink
////////////////////////////////////////////////////////////////////////////////

NS_IMETHODIMP
DocumentLoadListener::AsyncOnChannelRedirect(
    nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
    nsIAsyncVerifyRedirectCallback* aCallback) {
  LOG(("DocumentLoadListener AsyncOnChannelRedirect [this=%p, aFlags=%" PRIx32
       "]",
       this, aFlags));
  // We generally don't want to notify the content process about redirects,
  // so just update our channel and tell the callback that we're good to go.
  mChannel = aNewChannel;

  // Since we're redirecting away from aOldChannel, we should check if it
  // had a COOP mismatch, since we want the final result for this to
  // include the state of all channels we redirected through.
  nsCOMPtr<nsIHttpChannelInternal> httpChannel = do_QueryInterface(aOldChannel);
  if (httpChannel) {
    bool isCOOPMismatch = false;
    Unused << NS_WARN_IF(NS_FAILED(
        httpChannel->HasCrossOriginOpenerPolicyMismatch(&isCOOPMismatch)));
    mHasCrossOriginOpenerPolicyMismatch |= isCOOPMismatch;
  }

  // We don't need to confirm internal redirects or record any
  // history for them, so just immediately verify and return.
  if (aFlags & nsIChannelEventSink::REDIRECT_INTERNAL) {
    LOG(
        ("DocumentLoadListener AsyncOnChannelRedirect [this=%p] "
         "flags=REDIRECT_INTERNAL",
         this));
    aCallback->OnRedirectVerifyCallback(NS_OK);
    return NS_OK;
  }

  if (!net::ChannelIsPost(aOldChannel)) {
    AddURIVisit(aOldChannel, 0);

    nsCOMPtr<nsIURI> oldURI;
    aOldChannel->GetURI(getter_AddRefs(oldURI));
    nsDocShell::SaveLastVisit(aNewChannel, oldURI, aFlags);
  }
  mHaveVisibleRedirect |= true;

  LOG(
      ("DocumentLoadListener AsyncOnChannelRedirect [this=%p] "
       "mHaveVisibleRedirect=%c",
       this, mHaveVisibleRedirect ? 'T' : 'F'));

  // If this is a cross-origin redirect, then we should no longer allow
  // mixed content. The destination docshell checks this in its redirect
  // handling, but if we deliver to a new docshell (with a process switch)
  // then this doesn't happen.
  // Manually remove the allow mixed content flags.
  nsresult rv = nsContentUtils::CheckSameOrigin(aOldChannel, aNewChannel);
  if (NS_FAILED(rv)) {
    if (mLoadStateLoadType == LOAD_NORMAL_ALLOW_MIXED_CONTENT) {
      mLoadStateLoadType = LOAD_NORMAL;
    } else if (mLoadStateLoadType == LOAD_RELOAD_ALLOW_MIXED_CONTENT) {
      mLoadStateLoadType = LOAD_RELOAD_NORMAL;
    }
    MOZ_ASSERT(!LOAD_TYPE_HAS_FLAGS(
        mLoadStateLoadType, nsIWebNavigation::LOAD_FLAGS_ALLOW_MIXED_CONTENT));
  }

  // We need the original URI of the current channel to use to open the real
  // channel in the content process. Unfortunately we overwrite the original
  // uri of the new channel with the original pre-redirect URI, so grab
  // a copy of it now.
  aNewChannel->GetOriginalURI(getter_AddRefs(mChannelCreationURI));

  // Clear out our nsIParentChannel functions, since a normal parent
  // channel would actually redirect and not have those values on the new one.
  // We expect the URI classifier to run on the redirected channel with
  // the new URI and set these again.
  mIParentChannelFunctions.Clear();

#ifdef ANDROID
  nsCOMPtr<nsIURI> uriBeingLoaded =
      AntiTrackingUtils::MaybeGetDocumentURIBeingLoaded(mChannel);
  RefPtr<CanonicalBrowsingContext> bc =
      mParentChannelListener->GetBrowsingContext();

  RefPtr<MozPromise<bool, bool, false>> promise;
  nsCOMPtr<nsIWidget> widget = bc->GetParentProcessWidgetContaining();
  RefPtr<nsWindow> window = nsWindow::From(widget);

  if (window) {
    promise = window->OnLoadRequest(uriBeingLoaded,
                                    nsIBrowserDOMWindow::OPEN_CURRENTWINDOW,
                                    nsIWebNavigation::LOAD_FLAGS_IS_REDIRECT,
                                    nullptr, false, bc->IsTopContent());
  }

  if (promise) {
    RefPtr<nsIAsyncVerifyRedirectCallback> cb = aCallback;
    promise->Then(
        GetCurrentThreadSerialEventTarget(), __func__,
        [=](const MozPromise<bool, bool, false>::ResolveOrRejectValue& aValue) {
          if (aValue.IsResolve()) {
            bool handled = aValue.ResolveValue();
            if (handled) {
              cb->OnRedirectVerifyCallback(NS_ERROR_ABORT);
            } else {
              cb->OnRedirectVerifyCallback(NS_OK);
            }
          }
        });
  } else
#endif /* ANDROID */
  {
    aCallback->OnRedirectVerifyCallback(NS_OK);
  }
  return NS_OK;
}

// This method returns the cached result of running the Cross-Origin-Opener
// policy compare algorithm by calling ComputeCrossOriginOpenerPolicyMismatch
bool DocumentLoadListener::HasCrossOriginOpenerPolicyMismatch() const {
  // If we found a COOP mismatch on an earlier channel and then
  // redirected away from that, we should use that result.
  if (mHasCrossOriginOpenerPolicyMismatch) {
    return true;
  }

  nsCOMPtr<nsIHttpChannelInternal> httpChannel = do_QueryInterface(mChannel);
  if (!httpChannel) {
    // Not an nsIHttpChannelInternal assume it's okay to switch.
    return false;
  }

  bool isCOOPMismatch = false;
  Unused << NS_WARN_IF(NS_FAILED(
      httpChannel->HasCrossOriginOpenerPolicyMismatch(&isCOOPMismatch)));
  return isCOOPMismatch;
}

auto DocumentLoadListener::AttachStreamFilter(base::ProcessId aChildProcessId)
    -> RefPtr<ChildEndpointPromise> {
  LOG(("DocumentLoadListener AttachStreamFilter [this=%p]", this));

  StreamFilterRequest* request = mStreamFilterRequests.AppendElement();
  request->mPromise = new ChildEndpointPromise::Private(__func__);
  request->mChildProcessId = aChildProcessId;
  return request->mPromise;
}

already_AddRefed<nsIBrowser> DocumentLoadListener::GetBrowser() {
  CanonicalBrowsingContext* bc = GetBrowsingContext();
  if (!bc || !bc->IsTopContent()) {
    return nullptr;
  }

  nsCOMPtr<nsIBrowser> browser;
  RefPtr<Element> currentElement = bc->GetEmbedderElement();

  // In Responsive Design Mode, mFrameElement will be the <iframe mozbrowser>,
  // but we want the <xul:browser> that it is embedded in.
  while (currentElement) {
    browser = currentElement->AsBrowser();
    if (browser) {
      break;
    }

    BrowsingContext* browsingContext =
        currentElement->OwnerDoc()->GetBrowsingContext();
    currentElement =
        browsingContext ? browsingContext->GetEmbedderElement() : nullptr;
  }

  return browser.forget();
}

already_AddRefed<nsIWebProgressListener>
DocumentLoadListener::GetRemoteWebProgressListener(
    nsIWebProgress** aWebProgress, nsIRequest** aRequest) {
  nsCOMPtr<nsIBrowser> browser = GetBrowser();
  if (!browser) {
    return nullptr;
  }

  nsCOMPtr<nsIWebProgress> manager;
  nsresult rv = browser->GetRemoteWebProgressManager(getter_AddRefs(manager));
  if (NS_FAILED(rv)) {
    return nullptr;
  }

  nsCOMPtr<nsIWebProgressListener> listener = do_QueryInterface(manager);
  if (!listener) {
    // We are no longer remote so we cannot forward this event.
    return nullptr;
  }

  nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
  nsCOMPtr<nsIWebProgress> webProgress = new RemoteWebProgress(
      manager, loadInfo->GetOuterWindowID(),
      /* aInnerDOMWindowID = */ 0, mLoadStateLoadType, true, true);

  nsCOMPtr<nsIURI> uri, originalUri;
  mChannel->GetURI(getter_AddRefs(uri));
  mChannel->GetOriginalURI(getter_AddRefs(originalUri));
  nsCString matchedList;

  nsCOMPtr<nsIRequest> request = MakeAndAddRef<RemoteWebProgressRequest>(
      uri, originalUri, matchedList, Nothing());

  webProgress.forget(aWebProgress);
  request.forget(aRequest);
  return listener.forget();
}

NS_IMETHODIMP DocumentLoadListener::OnProgress(nsIRequest* aRequest,
                                               int64_t aProgress,
                                               int64_t aProgressMax) {
  return NS_OK;
}

NS_IMETHODIMP DocumentLoadListener::OnStatus(nsIRequest* aRequest,
                                             nsresult aStatus,
                                             const char16_t* aStatusArg) {
  nsCOMPtr<nsIWebProgress> webProgress;
  nsCOMPtr<nsIRequest> request;
  nsCOMPtr<nsIWebProgressListener> listener = GetRemoteWebProgressListener(
      getter_AddRefs(webProgress), getter_AddRefs(request));
  if (!listener) {
    return NS_OK;
  }

  const nsString message(aStatusArg);

  NS_DispatchToMainThread(
      NS_NewRunnableFunction("DocumentLoadListener::FireStateChange", [=]() {
        Unused << listener->OnStatusChange(webProgress, request, aStatus,
                                           message.get());
      }));
  return NS_OK;
}

}  // namespace net
}  // namespace mozilla

#undef LOG