Navigable.cpp 110 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185
  1. /*
  2. * Copyright (c) 2022-2024, Andreas Kling <andreas@ladybird.org>
  3. * Copyright (c) 2023, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <LibWeb/CSS/SystemColor.h>
  8. #include <LibWeb/Crypto/Crypto.h>
  9. #include <LibWeb/DOM/Document.h>
  10. #include <LibWeb/DOM/DocumentLoading.h>
  11. #include <LibWeb/DOM/Event.h>
  12. #include <LibWeb/DOM/Range.h>
  13. #include <LibWeb/Fetch/Fetching/Fetching.h>
  14. #include <LibWeb/Fetch/Infrastructure/FetchAlgorithms.h>
  15. #include <LibWeb/Fetch/Infrastructure/FetchController.h>
  16. #include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h>
  17. #include <LibWeb/Fetch/Infrastructure/URL.h>
  18. #include <LibWeb/HTML/BrowsingContext.h>
  19. #include <LibWeb/HTML/DocumentState.h>
  20. #include <LibWeb/HTML/HTMLIFrameElement.h>
  21. #include <LibWeb/HTML/HistoryHandlingBehavior.h>
  22. #include <LibWeb/HTML/Navigable.h>
  23. #include <LibWeb/HTML/Navigation.h>
  24. #include <LibWeb/HTML/NavigationParams.h>
  25. #include <LibWeb/HTML/POSTResource.h>
  26. #include <LibWeb/HTML/Parser/HTMLParser.h>
  27. #include <LibWeb/HTML/SandboxingFlagSet.h>
  28. #include <LibWeb/HTML/Scripting/ClassicScript.h>
  29. #include <LibWeb/HTML/SessionHistoryEntry.h>
  30. #include <LibWeb/HTML/StructuredSerialize.h>
  31. #include <LibWeb/HTML/TraversableNavigable.h>
  32. #include <LibWeb/HTML/Window.h>
  33. #include <LibWeb/HTML/WindowProxy.h>
  34. #include <LibWeb/Infra/Strings.h>
  35. #include <LibWeb/Layout/Node.h>
  36. #include <LibWeb/Loader/GeneratedPagesLoader.h>
  37. #include <LibWeb/Page/Page.h>
  38. #include <LibWeb/Painting/Paintable.h>
  39. #include <LibWeb/Painting/ViewportPaintable.h>
  40. #include <LibWeb/Platform/EventLoopPlugin.h>
  41. #include <LibWeb/Selection/Selection.h>
  42. #include <LibWeb/XHR/FormData.h>
  43. namespace Web::HTML {
  44. JS_DEFINE_ALLOCATOR(Navigable);
  45. class ResponseHolder : public JS::Cell {
  46. JS_CELL(ResponseHolder, JS::Cell);
  47. JS_DECLARE_ALLOCATOR(ResponseHolder);
  48. public:
  49. [[nodiscard]] static JS::NonnullGCPtr<ResponseHolder> create(JS::VM& vm)
  50. {
  51. return vm.heap().allocate_without_realm<ResponseHolder>();
  52. }
  53. [[nodiscard]] JS::GCPtr<Fetch::Infrastructure::Response> response() const { return m_response; }
  54. void set_response(JS::GCPtr<Fetch::Infrastructure::Response> response) { m_response = response; }
  55. virtual void visit_edges(Cell::Visitor& visitor) override
  56. {
  57. Base::visit_edges(visitor);
  58. visitor.visit(m_response);
  59. }
  60. private:
  61. JS::GCPtr<Fetch::Infrastructure::Response> m_response;
  62. };
  63. JS_DEFINE_ALLOCATOR(ResponseHolder);
  64. HashTable<Navigable*>& all_navigables()
  65. {
  66. static HashTable<Navigable*> set;
  67. return set;
  68. }
  69. // https://html.spec.whatwg.org/multipage/document-sequences.html#child-navigable
  70. Vector<JS::Handle<Navigable>> Navigable::child_navigables() const
  71. {
  72. Vector<JS::Handle<Navigable>> results;
  73. for (auto& entry : all_navigables()) {
  74. if (entry->current_session_history_entry()->step() == SessionHistoryEntry::Pending::Tag)
  75. continue;
  76. if (entry->parent() == this)
  77. results.append(entry);
  78. }
  79. return results;
  80. }
  81. bool Navigable::is_traversable() const
  82. {
  83. return is<TraversableNavigable>(*this);
  84. }
  85. bool Navigable::is_ancestor_of(JS::NonnullGCPtr<Navigable> other) const
  86. {
  87. for (auto ancestor = other->parent(); ancestor; ancestor = ancestor->parent()) {
  88. if (ancestor == this)
  89. return true;
  90. }
  91. return false;
  92. }
  93. Navigable::Navigable(JS::NonnullGCPtr<Page> page)
  94. : m_page(page)
  95. , m_event_handler({}, *this)
  96. {
  97. all_navigables().set(this);
  98. }
  99. Navigable::~Navigable()
  100. {
  101. all_navigables().remove(this);
  102. }
  103. void Navigable::visit_edges(Cell::Visitor& visitor)
  104. {
  105. Base::visit_edges(visitor);
  106. visitor.visit(m_page);
  107. visitor.visit(m_parent);
  108. visitor.visit(m_current_session_history_entry);
  109. visitor.visit(m_active_session_history_entry);
  110. visitor.visit(m_container);
  111. m_event_handler.visit_edges(visitor);
  112. }
  113. void Navigable::set_delaying_load_events(bool value)
  114. {
  115. if (value) {
  116. auto document = container_document();
  117. VERIFY(document);
  118. m_delaying_the_load_event.emplace(*document);
  119. } else {
  120. m_delaying_the_load_event.clear();
  121. }
  122. }
  123. JS::GCPtr<Navigable> Navigable::navigable_with_active_document(JS::NonnullGCPtr<DOM::Document> document)
  124. {
  125. for (auto* navigable : all_navigables()) {
  126. if (navigable->active_document() == document)
  127. return navigable;
  128. }
  129. return nullptr;
  130. }
  131. // https://html.spec.whatwg.org/multipage/document-sequences.html#initialize-the-navigable
  132. ErrorOr<void> Navigable::initialize_navigable(JS::NonnullGCPtr<DocumentState> document_state, JS::GCPtr<Navigable> parent)
  133. {
  134. static int next_id = 0;
  135. m_id = TRY(String::number(next_id++));
  136. // 1. Assert: documentState's document is non-null.
  137. VERIFY(document_state->document());
  138. // 2. Let entry be a new session history entry, with
  139. JS::NonnullGCPtr<SessionHistoryEntry> entry = *heap().allocate_without_realm<SessionHistoryEntry>();
  140. // URL: document's URL
  141. entry->set_url(document_state->document()->url());
  142. // document state: documentState
  143. entry->set_document_state(document_state);
  144. // 3. Set navigable's current session history entry to entry.
  145. m_current_session_history_entry = entry;
  146. // 4. Set navigable's active session history entry to entry.
  147. m_active_session_history_entry = entry;
  148. // 5. Set navigable's parent to parent.
  149. m_parent = parent;
  150. return {};
  151. }
  152. // https://html.spec.whatwg.org/multipage/browsing-the-web.html#getting-the-target-history-entry
  153. JS::GCPtr<SessionHistoryEntry> Navigable::get_the_target_history_entry(int target_step) const
  154. {
  155. // 1. Let entries be the result of getting session history entries for navigable.
  156. auto& entries = get_session_history_entries();
  157. // 2. Return the item in entries that has the greatest step less than or equal to step.
  158. JS::GCPtr<SessionHistoryEntry> result = nullptr;
  159. for (auto& entry : entries) {
  160. auto entry_step = entry->step().get<int>();
  161. if (entry_step <= target_step) {
  162. if (!result || result->step().get<int>() < entry_step) {
  163. result = entry;
  164. }
  165. }
  166. }
  167. return result;
  168. }
  169. // https://html.spec.whatwg.org/multipage/browsing-the-web.html#activate-history-entry
  170. void Navigable::activate_history_entry(JS::GCPtr<SessionHistoryEntry> entry)
  171. {
  172. // FIXME: 1. Save persisted state to the navigable's active session history entry.
  173. // 2. Let newDocument be entry's document.
  174. JS::GCPtr<DOM::Document> new_document = entry->document().ptr();
  175. // 3. Assert: newDocument's is initial about:blank is false, i.e., we never traverse
  176. // back to the initial about:blank Document because it always gets replaced when we
  177. // navigate away from it.
  178. VERIFY(!new_document->is_initial_about_blank());
  179. // 4. Set navigable's active session history entry to entry.
  180. m_active_session_history_entry = entry;
  181. // 5. Make active newDocument.
  182. new_document->make_active();
  183. }
  184. // https://html.spec.whatwg.org/multipage/document-sequences.html#nav-document
  185. JS::GCPtr<DOM::Document> Navigable::active_document()
  186. {
  187. // A navigable's active document is its active session history entry's document.
  188. return m_active_session_history_entry->document();
  189. }
  190. // https://html.spec.whatwg.org/multipage/document-sequences.html#nav-bc
  191. JS::GCPtr<BrowsingContext> Navigable::active_browsing_context()
  192. {
  193. // A navigable's active browsing context is its active document's browsing context.
  194. // If this navigable is a traversable navigable, then its active browsing context will be a top-level browsing context.
  195. if (auto document = active_document())
  196. return document->browsing_context();
  197. return nullptr;
  198. }
  199. // https://html.spec.whatwg.org/multipage/document-sequences.html#nav-wp
  200. JS::GCPtr<HTML::WindowProxy> Navigable::active_window_proxy()
  201. {
  202. // A navigable's active WindowProxy is its active browsing context's associated WindowProxy.
  203. if (auto browsing_context = active_browsing_context())
  204. return browsing_context->window_proxy();
  205. return nullptr;
  206. }
  207. // https://html.spec.whatwg.org/multipage/document-sequences.html#nav-window
  208. JS::GCPtr<HTML::Window> Navigable::active_window()
  209. {
  210. // A navigable's active window is its active WindowProxy's [[Window]].
  211. if (auto window_proxy = active_window_proxy())
  212. return window_proxy->window();
  213. return nullptr;
  214. }
  215. // https://html.spec.whatwg.org/multipage/document-sequences.html#nav-target
  216. String Navigable::target_name() const
  217. {
  218. // A navigable's target name is its active session history entry's document state's navigable target name.
  219. return active_session_history_entry()->document_state()->navigable_target_name();
  220. }
  221. // https://html.spec.whatwg.org/multipage/document-sequences.html#nav-container
  222. JS::GCPtr<NavigableContainer> Navigable::container() const
  223. {
  224. // The container of a navigable navigable is the navigable container whose nested navigable is navigable, or null if there is no such element.
  225. return NavigableContainer::navigable_container_with_content_navigable(const_cast<Navigable&>(*this));
  226. }
  227. // https://html.spec.whatwg.org/multipage/document-sequences.html#nav-container-document
  228. JS::GCPtr<DOM::Document> Navigable::container_document() const
  229. {
  230. auto container = this->container();
  231. // 1. If navigable's container is null, then return null.
  232. if (!container)
  233. return nullptr;
  234. // 2. Return navigable's container's node document.
  235. return container->document();
  236. }
  237. // https://html.spec.whatwg.org/multipage/document-sequences.html#nav-traversable
  238. JS::GCPtr<TraversableNavigable> Navigable::traversable_navigable() const
  239. {
  240. // 1. Let navigable be inputNavigable.
  241. auto navigable = const_cast<Navigable*>(this);
  242. // 2. While navigable is not a traversable navigable, set navigable to navigable's parent.
  243. while (navigable && !is<TraversableNavigable>(*navigable))
  244. navigable = navigable->parent();
  245. // 3. Return navigable.
  246. return static_cast<TraversableNavigable*>(navigable);
  247. }
  248. // https://html.spec.whatwg.org/multipage/document-sequences.html#nav-top
  249. JS::GCPtr<TraversableNavigable> Navigable::top_level_traversable()
  250. {
  251. // 1. Let navigable be inputNavigable.
  252. auto navigable = this;
  253. // 2. While navigable's parent is not null, set navigable to navigable's parent.
  254. while (navigable->parent())
  255. navigable = navigable->parent();
  256. // 3. Return navigable.
  257. return verify_cast<TraversableNavigable>(navigable);
  258. }
  259. // https://html.spec.whatwg.org/multipage/browsing-the-web.html#set-the-ongoing-navigation
  260. void Navigable::set_ongoing_navigation(Variant<Empty, Traversal, String> ongoing_navigation)
  261. {
  262. // 1. If navigable's ongoing navigation is equal to newValue, then return.
  263. if (m_ongoing_navigation == ongoing_navigation)
  264. return;
  265. // 2. Inform the navigation API about aborting navigation given navigable.
  266. inform_the_navigation_api_about_aborting_navigation();
  267. // 3. Set navigable's ongoing navigation to newValue.
  268. m_ongoing_navigation = ongoing_navigation;
  269. }
  270. // https://html.spec.whatwg.org/multipage/document-sequences.html#the-rules-for-choosing-a-navigable
  271. Navigable::ChosenNavigable Navigable::choose_a_navigable(StringView name, TokenizedFeature::NoOpener no_opener, ActivateTab activate_tab, Optional<TokenizedFeature::Map const&> window_features)
  272. {
  273. // NOTE: Implementation for step 7 here.
  274. JS::GCPtr<Navigable> same_name_navigable = nullptr;
  275. if (!Infra::is_ascii_case_insensitive_match(name, "_blank"sv)) {
  276. for (auto& n : all_navigables()) {
  277. if (n->target_name() == name && !n->has_been_destroyed()) {
  278. same_name_navigable = n;
  279. }
  280. }
  281. }
  282. // 1. Let chosen be null.
  283. JS::GCPtr<Navigable> chosen = nullptr;
  284. // 2. Let windowType be "existing or none".
  285. auto window_type = WindowType::ExistingOrNone;
  286. // 3. Let sandboxingFlagSet be current's active document's active sandboxing flag set.
  287. auto sandboxing_flag_set = active_document()->active_sandboxing_flag_set();
  288. // 4. If name is the empty string or an ASCII case-insensitive match for "_self", then set chosen to currentNavigable.
  289. if (name.is_empty() || Infra::is_ascii_case_insensitive_match(name, "_self"sv)) {
  290. chosen = this;
  291. }
  292. // 5. Otherwise, if name is an ASCII case-insensitive match for "_parent",
  293. // set chosen to currentNavigable's parent, if any, and currentNavigable otherwise.
  294. else if (Infra::is_ascii_case_insensitive_match(name, "_parent"sv)) {
  295. if (auto parent = this->parent())
  296. chosen = parent;
  297. else
  298. chosen = this;
  299. }
  300. // 6. Otherwise, if name is an ASCII case-insensitive match for "_top",
  301. // set chosen to currentNavigable's traversable navigable.
  302. else if (Infra::is_ascii_case_insensitive_match(name, "_top"sv)) {
  303. chosen = traversable_navigable();
  304. }
  305. // 7. Otherwise, if name is not an ASCII case-insensitive match for "_blank",
  306. // there exists a navigable whose target name is the same as name, currentNavigable's
  307. // active browsing context is familiar with that navigable's active browsing context,
  308. // and the user agent determines that the two browsing contexts are related enough that
  309. // it is ok if they reach each other, set chosen to that navigable. If there are multiple
  310. // matching navigables, the user agent should pick one in some arbitrary consistent manner,
  311. // such as the most recently opened, most recently focused, or more closely related, and set
  312. // chosen to it.
  313. else if (same_name_navigable != nullptr && (active_browsing_context()->is_familiar_with(*same_name_navigable->active_browsing_context()))) {
  314. // FIXME: Handle multiple name-match case
  315. // FIXME: When are these contexts 'not related enough' ?
  316. chosen = same_name_navigable;
  317. }
  318. // 8. Otherwise, a new top-level traversable is being requested, and what happens depends on the
  319. // user agent's configuration and abilities — it is determined by the rules given for the first
  320. // applicable option from the following list:
  321. else {
  322. // --> If current's active window does not have transient activation and the user agent has been configured to
  323. // not show popups (i.e., the user agent has a "popup blocker" enabled)
  324. if (active_window() && !active_window()->has_transient_activation() && traversable_navigable()->page().should_block_pop_ups()) {
  325. // FIXME: The user agent may inform the user that a popup has been blocked.
  326. dbgln("Pop-up blocked!");
  327. }
  328. // --> If sandboxingFlagSet has the sandboxed auxiliary navigation browsing context flag set
  329. else if (has_flag(sandboxing_flag_set, SandboxingFlagSet::SandboxedAuxiliaryNavigation)) {
  330. // FIXME: The user agent may report to a developer console that a popup has been blocked.
  331. dbgln("Pop-up blocked!");
  332. }
  333. // --> If the user agent has been configured such that in this instance it will create a new top-level traversable
  334. else if (true) { // FIXME: When is this the case?
  335. // 1. Set windowType to "new and unrestricted".
  336. window_type = WindowType::NewAndUnrestricted;
  337. // 2. Let currentDocument be currentNavigable's active document.
  338. auto current_document = active_document();
  339. // 3. If currentDocument's opener policy's value is "same-origin" or "same-origin-plus-COEP",
  340. // and currentDocument's origin is not same origin with currentDocument's relevant settings object's top-level origin, then:
  341. if ((current_document->opener_policy().value == OpenerPolicyValue::SameOrigin || current_document->opener_policy().value == OpenerPolicyValue::SameOriginPlusCOEP)
  342. && !current_document->origin().is_same_origin(relevant_settings_object(*current_document).top_level_origin)) {
  343. // 1. Set noopener to true.
  344. no_opener = TokenizedFeature::NoOpener::Yes;
  345. // 2. Set name to "_blank".
  346. name = "_blank"sv;
  347. // 3. Set windowType to "new with no opener".
  348. window_type = WindowType::NewWithNoOpener;
  349. }
  350. // NOTE: In the presence of an opener policy,
  351. // nested documents that are cross-origin with their top-level browsing context's active document always set noopener to true.
  352. // 4. Let chosen be null.
  353. chosen = nullptr;
  354. // 5. Let targetName be the empty string.
  355. String target_name;
  356. // 6. If name is not an ASCII case-insensitive match for "_blank", then set targetName to name.
  357. if (!Infra::is_ascii_case_insensitive_match(name, "_blank"sv))
  358. target_name = MUST(String::from_utf8(name));
  359. auto create_new_traversable_closure = [this, no_opener, target_name, activate_tab, window_features](JS::GCPtr<BrowsingContext> opener) -> JS::NonnullGCPtr<Navigable> {
  360. auto hints = WebViewHints::from_tokenised_features(window_features.value_or({}), traversable_navigable()->page());
  361. auto [page, window_handle] = traversable_navigable()->page().client().page_did_request_new_web_view(activate_tab, hints, no_opener);
  362. auto traversable = TraversableNavigable::create_a_new_top_level_traversable(*page, opener, target_name).release_value_but_fixme_should_propagate_errors();
  363. page->set_top_level_traversable(traversable);
  364. traversable->set_window_handle(window_handle);
  365. return traversable;
  366. };
  367. auto create_new_traversable = JS::create_heap_function(heap(), move(create_new_traversable_closure));
  368. // 7. If noopener is true, then set chosen to the result of creating a new top-level traversable given null and targetName.
  369. if (no_opener == TokenizedFeature::NoOpener::Yes) {
  370. chosen = create_new_traversable->function()(nullptr);
  371. }
  372. // 8. Otherwise:
  373. else {
  374. // 1. Set chosen to the result of creating a new top-level traversable given currentNavigable's active browsing context and targetName.
  375. chosen = create_new_traversable->function()(active_browsing_context());
  376. // FIXME: 2. If sandboxingFlagSet's sandboxed navigation browsing context flag is set,
  377. // then set chosen's active browsing context's one permitted sandboxed navigator to currentNavigable's active browsing context.
  378. }
  379. // FIXME: 5. If sandboxingFlagSet's sandbox propagates to auxiliary browsing contexts flag is set,
  380. // then all the flags that are set in sandboxingFlagSet must be set in chosen's active browsing context's popup sandboxing flag set.
  381. // Our BrowsingContexts do not have SandboxingFlagSets yet, only documents do
  382. }
  383. // --> If the user agent has been configured such that in this instance t will reuse current
  384. else if (false) { // FIXME: When is this the case?
  385. // Set chosen to current.
  386. chosen = *this;
  387. }
  388. // --> If the user agent has been configured such that in this instance it will not find a browsing context
  389. else if (false) { // FIXME: When is this the case?
  390. // Do nothing.
  391. }
  392. }
  393. return { chosen.ptr(), window_type };
  394. }
  395. // https://html.spec.whatwg.org/multipage/browsing-the-web.html#getting-session-history-entries
  396. Vector<JS::NonnullGCPtr<SessionHistoryEntry>>& Navigable::get_session_history_entries() const
  397. {
  398. // 1. Let traversable be navigable's traversable navigable.
  399. auto traversable = traversable_navigable();
  400. // FIXME 2. Assert: this is running within traversable's session history traversal queue.
  401. // 3. If navigable is traversable, return traversable's session history entries.
  402. if (this == traversable)
  403. return traversable->session_history_entries();
  404. // 4. Let docStates be an empty ordered set of document states.
  405. Vector<JS::GCPtr<DocumentState>> doc_states;
  406. // 5. For each entry of traversable's session history entries, append entry's document state to docStates.
  407. for (auto& entry : traversable->session_history_entries())
  408. doc_states.append(entry->document_state());
  409. // 6. For each docState of docStates:
  410. while (!doc_states.is_empty()) {
  411. auto doc_state = doc_states.take_first();
  412. // 1. For each nestedHistory of docState's nested histories:
  413. for (auto& nested_history : doc_state->nested_histories()) {
  414. // 1. If nestedHistory's id equals navigable's id, return nestedHistory's entries.
  415. if (nested_history.id == id())
  416. return nested_history.entries;
  417. // 2. For each entry of nestedHistory's entries, append entry's document state to docStates.
  418. for (auto& entry : nested_history.entries)
  419. doc_states.append(entry->document_state());
  420. }
  421. }
  422. VERIFY_NOT_REACHED();
  423. }
  424. // https://html.spec.whatwg.org/multipage/browsers.html#determining-navigation-params-policy-container
  425. static PolicyContainer determine_navigation_params_policy_container(URL::URL const& response_url,
  426. Optional<PolicyContainer> history_policy_container,
  427. Optional<PolicyContainer> initiator_policy_container,
  428. Optional<PolicyContainer> parent_policy_container,
  429. Optional<PolicyContainer> response_policy_container)
  430. {
  431. // NOTE: The clone a policy container AO is just a C++ copy
  432. // 1. If historyPolicyContainer is not null, then:
  433. if (history_policy_container.has_value()) {
  434. // FIXME: 1. Assert: responseURL requires storing the policy container in history.
  435. // 2. Return a clone of historyPolicyContainer.
  436. return *history_policy_container;
  437. }
  438. // 2. If responseURL is about:srcdoc, then:
  439. if (response_url == "about:srcdoc"sv) {
  440. // 1. Assert: parentPolicyContainer is not null.
  441. VERIFY(parent_policy_container.has_value());
  442. // 2. Return a clone of parentPolicyContainer.
  443. return *parent_policy_container;
  444. }
  445. // 3. If responseURL is local and initiatorPolicyContainer is not null, then return a clone of initiatorPolicyContainer.
  446. if (Fetch::Infrastructure::is_local_url(response_url) && initiator_policy_container.has_value())
  447. return *initiator_policy_container;
  448. // 4. If responsePolicyContainer is not null, then return responsePolicyContainer.
  449. // FIXME: File a spec issue to say "a clone of" here for consistency
  450. if (response_policy_container.has_value())
  451. return *response_policy_container;
  452. // 5. Return a new policy container.
  453. return {};
  454. }
  455. // https://html.spec.whatwg.org/multipage/browsers.html#obtain-coop
  456. static OpenerPolicy obtain_an_opener_policy(JS::NonnullGCPtr<Fetch::Infrastructure::Response>, Fetch::Infrastructure::Request::ReservedClientType const& reserved_client)
  457. {
  458. // 1. Let policy be a new opener policy.
  459. OpenerPolicy policy = {};
  460. // AD-HOC: We don't yet setup environments in all cases
  461. if (!reserved_client)
  462. return policy;
  463. auto& reserved_environment = *reserved_client;
  464. // 2. If reservedEnvironment is a non-secure context, then return policy.
  465. if (is_non_secure_context(reserved_environment))
  466. return policy;
  467. // FIXME: We don't yet have the technology to extract structured data from Fetch headers
  468. // FIXME: 3. Let parsedItem be the result of getting a structured field value given `Cross-Origin-Opener-Policy` and "item" from response's header list.
  469. // FIXME: 4. If parsedItem is not null, then:
  470. // FIXME: nested steps...
  471. // FIXME: 5. Set parsedItem to the result of getting a structured field value given `Cross-Origin-Opener-Policy-Report-Only` and "item" from response's header list.
  472. // FIXME: 6. If parsedItem is not null, then:
  473. // FIXME: nested steps...
  474. // 7. Return policy.
  475. return policy;
  476. }
  477. // https://html.spec.whatwg.org/multipage/browsing-the-web.html#attempt-to-create-a-non-fetch-scheme-document
  478. static JS::GCPtr<DOM::Document> attempt_to_create_a_non_fetch_scheme_document(NonFetchSchemeNavigationParams const& params)
  479. {
  480. // FIXME: Implement this algorithm to hand off to external software or display inline content
  481. dbgln("(FIXME) Don't know how to navigate to {}", params.url);
  482. return nullptr;
  483. }
  484. // https://html.spec.whatwg.org/multipage/browsing-the-web.html#create-navigation-params-from-a-srcdoc-resource
  485. static WebIDL::ExceptionOr<JS::NonnullGCPtr<NavigationParams>> create_navigation_params_from_a_srcdoc_resource(JS::GCPtr<SessionHistoryEntry> entry, JS::GCPtr<Navigable> navigable, TargetSnapshotParams const& target_snapshot_params, Optional<String> navigation_id)
  486. {
  487. auto& vm = navigable->vm();
  488. VERIFY(navigable->active_window());
  489. auto& realm = navigable->active_window()->realm();
  490. // 1. Let documentResource be entry's document state's resource.
  491. auto document_resource = entry->document_state()->resource();
  492. VERIFY(document_resource.has<String>());
  493. // 2. Let response be a new response with
  494. // URL: about:srcdoc
  495. // header list: (`Content-Type`, `text/html`)
  496. // body: the UTF-8 encoding of documentResource, as a body
  497. auto response = Fetch::Infrastructure::Response::create(vm);
  498. response->url_list().append(URL::URL("about:srcdoc"));
  499. auto header = Fetch::Infrastructure::Header::from_string_pair("Content-Type"sv, "text/html"sv);
  500. response->header_list()->append(move(header));
  501. response->set_body(TRY(Fetch::Infrastructure::byte_sequence_as_body(realm, document_resource.get<String>().bytes())));
  502. // 3. Let responseOrigin be the result of determining the origin given response's URL, targetSnapshotParams's sandboxing flags, and entry's document state's origin.
  503. auto response_origin = determine_the_origin(response->url(), target_snapshot_params.sandboxing_flags, entry->document_state()->origin());
  504. // 4. Let coop be a new opener policy.
  505. OpenerPolicy coop = {};
  506. // 5. Let coopEnforcementResult be a new opener policy enforcement result with
  507. // url: response's URL
  508. // origin: responseOrigin
  509. // opener policy: coop
  510. OpenerPolicyEnforcementResult coop_enforcement_result {
  511. .url = *response->url(),
  512. .origin = response_origin,
  513. .opener_policy = coop
  514. };
  515. // 6. Let policyContainer be the result of determining navigation params policy container given response's URL,
  516. // entry's document state's history policy container, null, navigable's container document's policy container, and null.
  517. Optional<PolicyContainer> history_policy_container = entry->document_state()->history_policy_container().visit(
  518. [](PolicyContainer const& c) -> Optional<PolicyContainer> { return c; },
  519. [](DocumentState::Client) -> Optional<PolicyContainer> { return {}; });
  520. PolicyContainer policy_container;
  521. if (navigable->container()) {
  522. // NOTE: Specification assumes that only navigables corresponding to iframes can be navigated to about:srcdoc.
  523. // We also use srcdoc to implement load_html() for top level navigables so we need to null check container
  524. // because it might be null.
  525. policy_container = determine_navigation_params_policy_container(*response->url(), history_policy_container, {}, navigable->container_document()->policy_container(), {});
  526. }
  527. // 7. Return a new navigation params, with
  528. // id: navigationId
  529. // navigable: navigable
  530. // request: null
  531. // response: response
  532. // fetch controller: null
  533. // commit early hints: null
  534. // COOP enforcement result: coopEnforcementResult
  535. // reserved environment: null
  536. // origin: responseOrigin
  537. // policy container: policyContainer
  538. // final sandboxing flag set: targetSnapshotParams's sandboxing flags
  539. // opener policy: coop
  540. // FIXME: navigation timing type: navTimingType
  541. // about base URL: entry's document state's about base URL
  542. auto navigation_params = vm.heap().allocate_without_realm<NavigationParams>();
  543. navigation_params->id = move(navigation_id);
  544. navigation_params->navigable = navigable;
  545. navigation_params->response = response;
  546. navigation_params->coop_enforcement_result = move(coop_enforcement_result);
  547. navigation_params->origin = move(response_origin);
  548. navigation_params->policy_container = policy_container;
  549. navigation_params->final_sandboxing_flag_set = target_snapshot_params.sandboxing_flags;
  550. navigation_params->opener_policy = move(coop);
  551. navigation_params->about_base_url = entry->document_state()->about_base_url();
  552. return navigation_params;
  553. }
  554. // https://html.spec.whatwg.org/multipage/browsing-the-web.html#create-navigation-params-by-fetching
  555. static WebIDL::ExceptionOr<Navigable::NavigationParamsVariant> create_navigation_params_by_fetching(JS::GCPtr<SessionHistoryEntry> entry, JS::GCPtr<Navigable> navigable, SourceSnapshotParams const& source_snapshot_params, TargetSnapshotParams const& target_snapshot_params, CSPNavigationType csp_navigation_type, Optional<String> navigation_id)
  556. {
  557. auto& vm = navigable->vm();
  558. VERIFY(navigable->active_window());
  559. auto& realm = navigable->active_window()->realm();
  560. auto& active_document = *navigable->active_document();
  561. (void)csp_navigation_type;
  562. // FIXME: 1. Assert: this is running in parallel.
  563. // 2. Let documentResource be entry's document state's resource.
  564. auto document_resource = entry->document_state()->resource();
  565. // 3. Let request be a new request, with
  566. // url: entry's URL
  567. // client: sourceSnapshotParams's fetch client
  568. // destination: "document"
  569. // credentials mode: "include"
  570. // use-URL-credentials flag: set
  571. // redirect mode: "manual"
  572. // replaces client id: navigable's active document's relevant settings object's id
  573. // mode: "navigate"
  574. // referrer: entry's document state's request referrer
  575. // referrer policy: entry's document state's request referrer policy
  576. auto request = Fetch::Infrastructure::Request::create(vm);
  577. request->set_url(entry->url());
  578. request->set_client(source_snapshot_params.fetch_client);
  579. request->set_destination(Fetch::Infrastructure::Request::Destination::Document);
  580. request->set_credentials_mode(Fetch::Infrastructure::Request::CredentialsMode::Include);
  581. request->set_use_url_credentials(true);
  582. request->set_redirect_mode(Fetch::Infrastructure::Request::RedirectMode::Manual);
  583. request->set_replaces_client_id(active_document.relevant_settings_object().id);
  584. request->set_mode(Fetch::Infrastructure::Request::Mode::Navigate);
  585. request->set_referrer(entry->document_state()->request_referrer());
  586. // 4. If documentResource is a POST resource, then:
  587. if (auto* post_resource = document_resource.get_pointer<POSTResource>()) {
  588. // 1. Set request's method to `POST`.
  589. request->set_method(TRY_OR_THROW_OOM(vm, ByteBuffer::copy("POST"sv.bytes())));
  590. // 2. Set request's body to documentResource's request body.
  591. request->set_body(document_resource.get<POSTResource>().request_body.value());
  592. // 3. Set `Content-Type` to documentResource's request content-type in request's header list.
  593. auto request_content_type = [&]() {
  594. switch (post_resource->request_content_type) {
  595. case POSTResource::RequestContentType::ApplicationXWWWFormUrlencoded:
  596. return "application/x-www-form-urlencoded"sv;
  597. case POSTResource::RequestContentType::MultipartFormData:
  598. return "multipart/form-data"sv;
  599. case POSTResource::RequestContentType::TextPlain:
  600. return "text/plain"sv;
  601. default:
  602. VERIFY_NOT_REACHED();
  603. }
  604. }();
  605. StringBuilder request_content_type_buffer;
  606. if (!post_resource->request_content_type_directives.is_empty()) {
  607. request_content_type_buffer.append(request_content_type);
  608. for (auto const& directive : post_resource->request_content_type_directives)
  609. request_content_type_buffer.appendff("; {}={}", directive.type, directive.value);
  610. request_content_type = request_content_type_buffer.string_view();
  611. }
  612. auto header = Fetch::Infrastructure::Header::from_string_pair("Content-Type"sv, request_content_type);
  613. request->header_list()->append(move(header));
  614. }
  615. // 5. If entry's document state's reload pending is true, then set request's reload-navigation flag.
  616. if (entry->document_state()->reload_pending())
  617. request->set_reload_navigation(true);
  618. // 6. Otherwise, if entry's document state's ever populated is true, then set request's history-navigation flag.
  619. if (entry->document_state()->ever_populated())
  620. request->set_history_navigation(true);
  621. // 7. If sourceSnapshotParams's has transient activation is true, then set request's user-activation to true.
  622. if (source_snapshot_params.has_transient_activation)
  623. request->set_user_activation(true);
  624. // 8. If navigable's container is non-null:
  625. if (navigable->container() != nullptr) {
  626. // 1. If the navigable's container has a browsing context scope origin, then set request's origin to that browsing context scope origin.
  627. // FIXME: From "browsing context scope origin": This definition is broken and needs investigation to see what it was intended to express: see issue #4703.
  628. // The referenced issue suggests that it is a no-op to retrieve the browsing context scope origin.
  629. // 2. Set request's destination to navigable's container's local name.
  630. // FIXME: Are there other container types? If so, we need a helper here
  631. Web::Fetch::Infrastructure::Request::Destination destination = is<HTMLIFrameElement>(*navigable->container()) ? Web::Fetch::Infrastructure::Request::Destination::IFrame
  632. : Web::Fetch::Infrastructure::Request::Destination::Object;
  633. request->set_destination(destination);
  634. // 3. If sourceSnapshotParams's fetch client is navigable's container document's relevant settings object,
  635. // then set request's initiator type to navigable's container's local name.
  636. // NOTE: This ensure that only container-initiated navigations are reported to resource timing.
  637. if (source_snapshot_params.fetch_client == &navigable->container_document()->relevant_settings_object()) {
  638. // FIXME: Are there other container types? If so, we need a helper here
  639. Web::Fetch::Infrastructure::Request::InitiatorType initiator_type = is<HTMLIFrameElement>(*navigable->container()) ? Web::Fetch::Infrastructure::Request::InitiatorType::IFrame
  640. : Web::Fetch::Infrastructure::Request::InitiatorType::Object;
  641. request->set_initiator_type(initiator_type);
  642. }
  643. }
  644. // 9. Let response be null.
  645. // NOTE: We use a heap-allocated cell to hold the response pointer because the processResponse callback below
  646. // might use it after this stack is freed.
  647. auto response_holder = ResponseHolder::create(vm);
  648. // 10. Let responseOrigin be null.
  649. Optional<URL::Origin> response_origin;
  650. // 11. Let fetchController be null.
  651. JS::GCPtr<Fetch::Infrastructure::FetchController> fetch_controller = nullptr;
  652. // 12. Let coopEnforcementResult be a new opener policy enforcement result, with
  653. // - url: navigable's active document's URL
  654. // - origin: navigable's active document's origin
  655. // - opener policy: navigable's active document's opener policy
  656. // - current context is navigation source: true if navigable's active document's origin is same origin with
  657. // entry's document state's initiator origin otherwise false
  658. OpenerPolicyEnforcementResult coop_enforcement_result = {
  659. .url = active_document.url(),
  660. .origin = active_document.origin(),
  661. .opener_policy = active_document.opener_policy(),
  662. .current_context_is_navigation_source = entry->document_state()->initiator_origin().has_value() && active_document.origin().is_same_origin(*entry->document_state()->initiator_origin())
  663. };
  664. // 13. Let finalSandboxFlags be an empty sandboxing flag set.
  665. SandboxingFlagSet final_sandbox_flags = {};
  666. // 14. Let responsePolicyContainer be null.
  667. Optional<PolicyContainer> response_policy_container = {};
  668. // 15. Let responseCOOP be a new opener policy.
  669. OpenerPolicy response_coop = {};
  670. // 16. Let locationURL be null.
  671. ErrorOr<Optional<URL::URL>> location_url { OptionalNone {} };
  672. // 17. Let currentURL be request's current URL.
  673. URL::URL current_url = request->current_url();
  674. // 18. Let commitEarlyHints be null.
  675. Function<void(DOM::Document&)> commit_early_hints = nullptr;
  676. // 19. While true:
  677. while (true) {
  678. // FIXME: 1. If request's reserved client is not null and currentURL's origin is not the same as request's reserved client's creation URL's origin, then:
  679. // FIXME: 2. If request's reserved client is null, then:
  680. // FIXME: 3. If the result of should navigation request of type be blocked by Content Security Policy? given request and cspNavigationType is "Blocked", then set response to a network error and break. [CSP]
  681. // 4. Set response to null.
  682. response_holder->set_response(nullptr);
  683. // 5. If fetchController is null, then set fetchController to the result of fetching request,
  684. // with processEarlyHintsResponse set to processEarlyHintsResponseas defined below, processResponse
  685. // set to processResponse as defined below, and useParallelQueue set to true.
  686. if (!fetch_controller) {
  687. // FIXME: Let processEarlyHintsResponse be the following algorithm given a response earlyResponse:
  688. // Let processResponse be the following algorithm given a response fetchedResponse:
  689. auto process_response = [response_holder](JS::NonnullGCPtr<Fetch::Infrastructure::Response> fetch_response) {
  690. // 1. Set response to fetchedResponse.
  691. response_holder->set_response(fetch_response);
  692. };
  693. fetch_controller = TRY(Fetch::Fetching::fetch(
  694. realm,
  695. request,
  696. Fetch::Infrastructure::FetchAlgorithms::create(vm,
  697. {
  698. .process_request_body_chunk_length = {},
  699. .process_request_end_of_body = {},
  700. .process_early_hints_response = {},
  701. .process_response = move(process_response),
  702. .process_response_end_of_body = {},
  703. .process_response_consume_body = {},
  704. }),
  705. Fetch::Fetching::UseParallelQueue::Yes));
  706. }
  707. // 6. Otherwise, process the next manual redirect for fetchController.
  708. else {
  709. fetch_controller->process_next_manual_redirect();
  710. }
  711. // 7. Wait until either response is non-null, or navigable's ongoing navigation changes to no longer equal navigationId.
  712. HTML::main_thread_event_loop().spin_until([&]() {
  713. if (response_holder->response() != nullptr)
  714. return true;
  715. if (navigation_id.has_value() && (!navigable->ongoing_navigation().has<String>() || navigable->ongoing_navigation().get<String>() != *navigation_id))
  716. return true;
  717. return false;
  718. });
  719. // If the latter condition occurs, then abort fetchController, and return. Otherwise, proceed onward.
  720. if (navigation_id.has_value() && (!navigable->ongoing_navigation().has<String>() || navigable->ongoing_navigation().get<String>() != *navigation_id)) {
  721. fetch_controller->abort(realm, {});
  722. return Empty {};
  723. }
  724. // 8. If request's body is null, then set entry's document state's resource to null.
  725. if (!request->body().has<Empty>()) {
  726. entry->document_state()->set_resource(Empty {});
  727. }
  728. // FIXME 9. Set responsePolicyContainer to the result of creating a policy container from a fetch response given response and request's reserved client.
  729. // FIXME 10. Set finalSandboxFlags to the union of targetSnapshotParams's sandboxing flags and responsePolicyContainer's CSP list's CSP-derived sandboxing flags.
  730. // 11. Set responseOrigin to the result of determining the origin given response's URL, finalSandboxFlags, and entry's document state's initiator origin.
  731. response_origin = determine_the_origin(response_holder->response()->url(), final_sandbox_flags, entry->document_state()->initiator_origin());
  732. // 12. If navigable is a top-level traversable, then:
  733. if (navigable->is_top_level_traversable()) {
  734. // 1. Set responseCOOP to the result of obtaining an opener policy given response and request's reserved client.
  735. response_coop = obtain_an_opener_policy(*response_holder->response(), request->reserved_client());
  736. // FIXME: 2. Set coopEnforcementResult to the result of enforcing the response's opener policy given navigable's active browsing context,
  737. // response's URL, responseOrigin, responseCOOP, coopEnforcementResult and request's referrer.
  738. // FIXME: 3. If finalSandboxFlags is not empty and responseCOOP's value is not "unsafe-none", then set response to an appropriate network error and break.
  739. // NOTE: This results in a network error as one cannot simultaneously provide a clean slate to a response
  740. // using opener policy and sandbox the result of navigating to that response.
  741. }
  742. // 13. FIXME If response is not a network error, navigable is a child navigable, and the result of performing a cross-origin resource policy check
  743. // with navigable's container document's origin, navigable's container document's relevant settings object, request's destination, response,
  744. // and true is blocked, then set response to a network error and break.
  745. // NOTE: Here we're running the cross-origin resource policy check against the parent navigable rather than navigable itself
  746. // This is because we care about the same-originness of the embedded content against the parent context, not the navigation source.
  747. // 14. Set locationURL to response's location URL given currentURL's fragment.
  748. location_url = response_holder->response()->location_url(current_url.fragment());
  749. VERIFY(!location_url.is_error());
  750. // 15. If locationURL is failure or null, then break.
  751. if (location_url.is_error() || !location_url.value().has_value()) {
  752. break;
  753. }
  754. // 16. Assert: locationURL is a URL.
  755. VERIFY(location_url.value()->is_valid());
  756. // 17. Set entry's classic history API state to StructuredSerializeForStorage(null).
  757. entry->set_classic_history_api_state(MUST(structured_serialize_for_storage(vm, JS::js_null())));
  758. // 18. Let oldDocState be entry's document state.
  759. auto old_doc_state = entry->document_state();
  760. // 19. Set entry's document state to a new document state, with
  761. // history policy container: a clone of the oldDocState's history policy container if it is non-null; null otherwise
  762. // request referrer: oldDocState's request referrer
  763. // request referrer policy: oldDocState's request referrer policy
  764. // origin: oldDocState's origin
  765. // resource: oldDocState's resource
  766. // ever populated: oldDocState's ever populated
  767. // navigable target name: oldDocState's navigable target name
  768. auto new_document_state = navigable->heap().allocate_without_realm<DocumentState>();
  769. new_document_state->set_history_policy_container(old_doc_state->history_policy_container());
  770. new_document_state->set_request_referrer(old_doc_state->request_referrer());
  771. new_document_state->set_request_referrer_policy(old_doc_state->request_referrer_policy());
  772. new_document_state->set_origin(old_doc_state->origin());
  773. new_document_state->set_resource(old_doc_state->resource());
  774. new_document_state->set_ever_populated(old_doc_state->ever_populated());
  775. new_document_state->set_navigable_target_name(old_doc_state->navigable_target_name());
  776. entry->set_document_state(new_document_state);
  777. // 20. If locationURL's scheme is not an HTTP(S) scheme, then:
  778. if (!Fetch::Infrastructure::is_http_or_https_scheme(location_url.value()->scheme())) {
  779. // 1. Set entry's document state's resource to null.
  780. entry->document_state()->set_resource(Empty {});
  781. // 2. Break.
  782. break;
  783. }
  784. // 21. Set currentURL to locationURL.
  785. current_url = location_url.value().value();
  786. // 22. Set entry's URL to currentURL.
  787. entry->set_url(current_url);
  788. }
  789. // 20. If locationURL is a URL whose scheme is not a fetch scheme, then return a new non-fetch scheme navigation params, with
  790. if (!location_url.is_error() && location_url.value().has_value() && !Fetch::Infrastructure::is_fetch_scheme(location_url.value().value().scheme())) {
  791. // - id: navigationId
  792. // - navigable: navigable
  793. // - URL: locationURL
  794. // - target snapshot sandboxing flags: targetSnapshotParams's sandboxing flags
  795. // - source snapshot has transient activation: sourceSnapshotParams's has transient activation
  796. // - initiator origin: responseOrigin
  797. // FIXME: - navigation timing type: navTimingType
  798. auto navigation_params = vm.heap().allocate_without_realm<NonFetchSchemeNavigationParams>();
  799. navigation_params->id = navigation_id;
  800. navigation_params->navigable = navigable;
  801. navigation_params->url = location_url.release_value().value();
  802. navigation_params->target_snapshot_sandboxing_flags = target_snapshot_params.sandboxing_flags;
  803. navigation_params->source_snapshot_has_transient_activation = source_snapshot_params.has_transient_activation;
  804. navigation_params->initiator_origin = move(*response_origin);
  805. return navigation_params;
  806. }
  807. // 21. If any of the following are true:
  808. // - response is a network error;
  809. // - locationURL is failure; or
  810. // - locationURL is a URL whose scheme is a fetch scheme
  811. // then return null.
  812. if (response_holder->response()->is_network_error()) {
  813. // AD-HOC: We pass the error message if we have one in NullWithError
  814. if (response_holder->response()->network_error_message().has_value() && !response_holder->response()->network_error_message().value().is_null())
  815. return response_holder->response()->network_error_message().value();
  816. else
  817. return Empty {};
  818. } else if (location_url.is_error() || (location_url.value().has_value() && Fetch::Infrastructure::is_fetch_scheme(location_url.value().value().scheme())))
  819. return Empty {};
  820. // 22. Assert: locationURL is null and response is not a network error.
  821. VERIFY(!location_url.value().has_value());
  822. VERIFY(!response_holder->response()->is_network_error());
  823. // 23. Let resultPolicyContainer be the result of determining navigation params policy container given response's URL,
  824. // entry's document state's history policy container, sourceSnapshotParams's source policy container, null, and responsePolicyContainer.
  825. Optional<PolicyContainer> history_policy_container = entry->document_state()->history_policy_container().visit(
  826. [](PolicyContainer const& c) -> Optional<PolicyContainer> { return c; },
  827. [](DocumentState::Client) -> Optional<PolicyContainer> { return {}; });
  828. auto result_policy_container = determine_navigation_params_policy_container(*response_holder->response()->url(), history_policy_container, source_snapshot_params.source_policy_container, {}, response_policy_container);
  829. // 24. If navigable's container is an iframe, and response's timing allow passed flag is set, then set container's pending resource-timing start time to null.
  830. if (navigable->container() && is<HTML::HTMLIFrameElement>(*navigable->container()) && response_holder->response()->timing_allow_passed())
  831. static_cast<HTML::HTMLIFrameElement&>(*navigable->container()).set_pending_resource_start_time({});
  832. // 25. Return a new navigation params, with
  833. // id: navigationId
  834. // navigable: navigable
  835. // request: request
  836. // response: response
  837. // fetch controller: fetchController
  838. // commit early hints: commitEarlyHints
  839. // opener policy: responseCOOP
  840. // reserved environment: request's reserved client
  841. // origin: responseOrigin
  842. // policy container: resultPolicyContainer
  843. // final sandboxing flag set: finalSandboxFlags
  844. // COOP enforcement result: coopEnforcementResult
  845. // FIXME: navigation timing type: navTimingType
  846. // about base URL: entry's document state's about base URL
  847. auto navigation_params = vm.heap().allocate_without_realm<NavigationParams>();
  848. navigation_params->id = navigation_id;
  849. navigation_params->navigable = navigable;
  850. navigation_params->request = request;
  851. navigation_params->response = *response_holder->response();
  852. navigation_params->fetch_controller = fetch_controller;
  853. navigation_params->commit_early_hints = move(commit_early_hints);
  854. navigation_params->coop_enforcement_result = coop_enforcement_result;
  855. navigation_params->reserved_environment = request->reserved_client();
  856. navigation_params->origin = *response_origin;
  857. navigation_params->policy_container = result_policy_container;
  858. navigation_params->final_sandboxing_flag_set = final_sandbox_flags;
  859. navigation_params->opener_policy = response_coop;
  860. navigation_params->about_base_url = entry->document_state()->about_base_url();
  861. return navigation_params;
  862. }
  863. // https://html.spec.whatwg.org/multipage/browsing-the-web.html#attempt-to-populate-the-history-entry's-document
  864. WebIDL::ExceptionOr<void> Navigable::populate_session_history_entry_document(
  865. JS::GCPtr<SessionHistoryEntry> entry,
  866. SourceSnapshotParams const& source_snapshot_params,
  867. TargetSnapshotParams const& target_snapshot_params,
  868. Optional<String> navigation_id,
  869. Navigable::NavigationParamsVariant navigation_params,
  870. CSPNavigationType csp_navigation_type,
  871. bool allow_POST,
  872. JS::GCPtr<JS::HeapFunction<void()>> completion_steps)
  873. {
  874. // AD-HOC: Not in the spec but subsequent steps will fail if the navigable doesn't have an active window.
  875. if (!active_window())
  876. return {};
  877. // FIXME: 1. Assert: this is running in parallel.
  878. // 2. Assert: if navigationParams is non-null, then navigationParams's response is non-null.
  879. // NavigationParams' response field is NonnullGCPtr
  880. if (!navigation_params.has<Empty>())
  881. VERIFY(navigation_params.has<JS::NonnullGCPtr<NavigationParams>>());
  882. // 3. Let currentBrowsingContext be navigable's active browsing context.
  883. [[maybe_unused]] auto current_browsing_context = active_browsing_context();
  884. // 4. Let documentResource be entry's document state's resource.
  885. auto document_resource = entry->document_state()->resource();
  886. // 5. If navigationParams is null, then:
  887. if (navigation_params.has<Empty>()) {
  888. // 1. If documentResource is a string, then set navigationParams to the result
  889. // of creating navigation params from a srcdoc resource given entry, navigable,
  890. // targetSnapshotParams, navigationId, and navTimingType.
  891. if (document_resource.has<String>()) {
  892. navigation_params = TRY(create_navigation_params_from_a_srcdoc_resource(entry, this, target_snapshot_params, navigation_id));
  893. }
  894. // 2. Otherwise, if all of the following are true:
  895. // - entry's URL's scheme is a fetch scheme; and
  896. // - documentResource is null, or allowPOST is true and documentResource's request body is not failure (FIXME: check if request body is not failure)
  897. // then set navigationParams to the result of creating navigation params by fetching given entry, navigable, sourceSnapshotParams, targetSnapshotParams, cspNavigationType, navigationId, and navTimingType.
  898. else if (Fetch::Infrastructure::is_fetch_scheme(entry->url().scheme()) && (document_resource.has<Empty>() || allow_POST)) {
  899. navigation_params = TRY(create_navigation_params_by_fetching(entry, this, source_snapshot_params, target_snapshot_params, csp_navigation_type, navigation_id));
  900. }
  901. // 3. Otherwise, if entry's URL's scheme is not a fetch scheme, then set navigationParams to a new non-fetch scheme navigation params, with:
  902. else if (!Fetch::Infrastructure::is_fetch_scheme(entry->url().scheme())) {
  903. // - id: navigationId
  904. // - navigable: navigable
  905. // - URL: entry's URL
  906. // - target snapshot sandboxing flags: targetSnapshotParams's sandboxing flags
  907. // - source snapshot has transient activation: sourceSnapshotParams's has transient activation
  908. // - initiator origin: entry's document state's initiator origin
  909. // FIXME: - navigation timing type: navTimingType
  910. auto non_fetching_scheme_navigation_params = vm().heap().allocate_without_realm<NonFetchSchemeNavigationParams>();
  911. non_fetching_scheme_navigation_params->id = navigation_id;
  912. non_fetching_scheme_navigation_params->navigable = this;
  913. non_fetching_scheme_navigation_params->url = entry->url();
  914. non_fetching_scheme_navigation_params->target_snapshot_sandboxing_flags = target_snapshot_params.sandboxing_flags;
  915. non_fetching_scheme_navigation_params->source_snapshot_has_transient_activation = source_snapshot_params.has_transient_activation;
  916. non_fetching_scheme_navigation_params->initiator_origin = *entry->document_state()->initiator_origin();
  917. navigation_params = non_fetching_scheme_navigation_params;
  918. }
  919. }
  920. // AD-HOC: Not in the spec but subsequent steps will fail if the navigable doesn't have an active window.
  921. if (!active_window())
  922. return {};
  923. // 6. Queue a global task on the navigation and traversal task source, given navigable's active window, to run these steps:
  924. queue_global_task(Task::Source::NavigationAndTraversal, *active_window(), JS::create_heap_function(heap(), [this, entry, navigation_params = move(navigation_params), navigation_id, completion_steps]() mutable {
  925. // NOTE: This check is not in the spec but we should not continue navigation if navigable has been destroyed.
  926. if (has_been_destroyed())
  927. return;
  928. // 1. If navigable's ongoing navigation no longer equals navigationId, then run completionSteps and abort these steps.
  929. if (navigation_id.has_value() && (!ongoing_navigation().has<String>() || ongoing_navigation().get<String>() != *navigation_id)) {
  930. if (completion_steps)
  931. completion_steps->function()();
  932. return;
  933. }
  934. // 2. Let saveExtraDocumentState be true.
  935. auto saveExtraDocumentState = true;
  936. // 3. If navigationParams is a non-fetch scheme navigation params, then:
  937. if (navigation_params.has<JS::NonnullGCPtr<NonFetchSchemeNavigationParams>>()) {
  938. // 1. Set entry's document state's document to the result of running attempt to create a non-fetch scheme document given navigationParams.
  939. entry->document_state()->set_document(attempt_to_create_a_non_fetch_scheme_document(navigation_params.get<JS::NonnullGCPtr<NonFetchSchemeNavigationParams>>()));
  940. if (entry->document()) {
  941. entry->document_state()->set_ever_populated(true);
  942. }
  943. // 2. Set saveExtraDocumentState to false.
  944. saveExtraDocumentState = false;
  945. }
  946. // 4. Otherwise, if any of the following are true:
  947. // - navigationParams is null;
  948. // - FIXME: the result of should navigation response to navigation request of type in target be blocked by Content Security Policy? given navigationParams's request, navigationParams's response, navigationParams's policy container's CSP list, cspNavigationType, and navigable is "Blocked";
  949. // - FIXME: navigationParams's reserved environment is non-null and the result of checking a navigation response's adherence to its embedder policy given navigationParams's response, navigable, and navigationParams's policy container's embedder policy is false; or
  950. // - FIXME: the result of checking a navigation response's adherence to `X-Frame-Options` given navigationParams's response, navigable, navigationParams's policy container's CSP list, and navigationParams's origin is false,
  951. if (navigation_params.has<Empty>() || navigation_params.has<NullWithError>()) {
  952. // 1. Set entry's document state's document to the result of creating a document for inline content that doesn't have a DOM, given navigable, null, and navTimingType. The inline content should indicate to the user the sort of error that occurred.
  953. auto error_message = navigation_params.has<NullWithError>() ? navigation_params.get<NullWithError>() : "Unknown error"sv;
  954. auto error_html = load_error_page(entry->url(), error_message).release_value_but_fixme_should_propagate_errors();
  955. entry->document_state()->set_document(create_document_for_inline_content(this, navigation_id, [error_html](auto& document) {
  956. auto parser = HTML::HTMLParser::create(document, error_html, "utf-8"sv);
  957. document.set_url(URL::URL("about:error"));
  958. parser->run();
  959. }));
  960. // 2. Make document unsalvageable given entry's document state's document and "navigation-failure".
  961. entry->document()->make_unsalvageable("navigation-failure"_string);
  962. // 3. Set saveExtraDocumentState to false.
  963. saveExtraDocumentState = false;
  964. // 4. If navigationParams is not null, then:
  965. if (navigation_params.has<Empty>()) {
  966. // FIXME: 1. Run the environment discarding steps for navigationParams's reserved environment.
  967. // FIXME: 2. Invoke WebDriver BiDi navigation failed with currentBrowsingContext and a new WebDriver BiDi navigation status whose id is navigationId, status is "canceled", and url is navigationParams's response's URL.
  968. }
  969. }
  970. // FIXME: 5. Otherwise, if navigationParams's response has a `Content-Disposition`
  971. // header specifying the attachment disposition type, then:
  972. // 6. Otherwise, if navigationParams's response's status is not 204 and is not 205, then set entry's document state's document to the result of
  973. // loading a document given navigationParams, sourceSnapshotParams, and entry's document state's initiator origin.
  974. else if (navigation_params.get<JS::NonnullGCPtr<NavigationParams>>()->response->status() != 204 && navigation_params.get<JS::NonnullGCPtr<NavigationParams>>()->response->status() != 205) {
  975. auto document = load_document(navigation_params.get<JS::NonnullGCPtr<NavigationParams>>());
  976. entry->document_state()->set_document(document);
  977. }
  978. // 7. If entry's document state's document is not null, then:
  979. if (entry->document()) {
  980. // 1. Set entry's document state's ever populated to true.
  981. entry->document_state()->set_ever_populated(true);
  982. // 2. If saveExtraDocumentState is true:
  983. if (saveExtraDocumentState) {
  984. // 1. Let document be entry's document state's document.
  985. auto document = entry->document();
  986. // 2. Set entry's document state's origin to document's origin.
  987. entry->document_state()->set_origin(document->origin());
  988. // FIXME: 3. If document's URL requires storing the policy container in history, then:
  989. }
  990. // 3. If entry's document state's request referrer is "client", and navigationParams is a navigation params (i.e., neither null nor a non-fetch scheme navigation params), then:
  991. if (entry->document_state()->request_referrer() == Fetch::Infrastructure::Request::Referrer::Client
  992. && (!navigation_params.has<Empty>() && Fetch::Infrastructure::is_fetch_scheme(entry->url().scheme()))) {
  993. // FIXME: 1. Assert: navigationParams's request is not null.
  994. // FIXME: 2. Set entry's document state's request referrer to navigationParams's request's referrer.
  995. }
  996. }
  997. // 8. Run completionSteps.
  998. if (completion_steps)
  999. completion_steps->function()();
  1000. }));
  1001. return {};
  1002. }
  1003. // To navigate a navigable navigable to a URL url using a Document sourceDocument,
  1004. // with an optional POST resource, string, or null documentResource (default null),
  1005. // an optional response-or-null response (default null), an optional boolean exceptionsEnabled (default false),
  1006. // an optional NavigationHistoryBehavior historyHandling (default "auto"),
  1007. // an optional serialized state-or-null navigationAPIState (default null),
  1008. // an optional entry list or null formDataEntryList (default null),
  1009. // an optional referrer policy referrerPolicy (default the empty string),
  1010. // and an optional user navigation involvement userInvolvement (default "none"):
  1011. // https://html.spec.whatwg.org/multipage/browsing-the-web.html#navigate
  1012. WebIDL::ExceptionOr<void> Navigable::navigate(NavigateParams params)
  1013. {
  1014. // AD-HOC: Not in the spec but subsequent steps will fail if the navigable doesn't have an active window.
  1015. if (!active_window())
  1016. return {};
  1017. auto const& url = params.url;
  1018. auto source_document = params.source_document;
  1019. auto const& document_resource = params.document_resource;
  1020. auto response = params.response;
  1021. auto exceptions_enabled = params.exceptions_enabled;
  1022. auto history_handling = params.history_handling;
  1023. auto const& navigation_api_state = params.navigation_api_state;
  1024. auto const& form_data_entry_list = params.form_data_entry_list;
  1025. auto referrer_policy = params.referrer_policy;
  1026. auto user_involvement = params.user_involvement;
  1027. auto& active_document = *this->active_document();
  1028. auto& realm = active_document.realm();
  1029. auto& vm = this->vm();
  1030. // 1. Let cspNavigationType be "form-submission" if formDataEntryList is non-null; otherwise "other".
  1031. auto csp_navigation_type = form_data_entry_list.has_value() ? CSPNavigationType::FormSubmission : CSPNavigationType::Other;
  1032. // 2. Let sourceSnapshotParams be the result of snapshotting source snapshot params given sourceDocument.
  1033. auto source_snapshot_params = source_document->snapshot_source_snapshot_params();
  1034. // 3. Let initiatorOriginSnapshot be sourceDocument's origin.
  1035. auto initiator_origin_snapshot = source_document->origin();
  1036. // 4. Let initiatorBaseURLSnapshot be sourceDocument's document base URL.
  1037. auto initiator_base_url_snapshot = source_document->base_url();
  1038. // 5. If sourceDocument's node navigable is not allowed by sandboxing to navigate navigable given and sourceSnapshotParams, then:
  1039. if (!source_document->navigable()->allowed_by_sandboxing_to_navigate(*this, source_snapshot_params)) {
  1040. // 1. If exceptionsEnabled is true, then throw a "SecurityError" DOMException.
  1041. if (exceptions_enabled) {
  1042. return WebIDL::SecurityError::create(realm, "Source document's node navigable is not allowed to navigate"_fly_string);
  1043. }
  1044. // 2 Return.
  1045. return {};
  1046. }
  1047. // 6. Let navigationId be the result of generating a random UUID.
  1048. String navigation_id = TRY_OR_THROW_OOM(vm, Crypto::generate_random_uuid());
  1049. // FIXME: 7. If the surrounding agent is equal to navigable's active document's relevant agent, then continue these steps.
  1050. // Otherwise, queue a global task on the navigation and traversal task source given navigable's active window to continue these steps.
  1051. // 8. If navigable's active document's unload counter is greater than 0,
  1052. // then invoke WebDriver BiDi navigation failed with a WebDriver BiDi navigation status whose id is navigationId,
  1053. // status is "canceled", and url is url, and return.
  1054. if (active_document.unload_counter() > 0) {
  1055. // FIXME: invoke WebDriver BiDi navigation failed with a WebDriver BiDi navigation status whose id is navigationId,
  1056. // status is "canceled", and url is url
  1057. return {};
  1058. }
  1059. // 9. If historyHandling is "auto", then:
  1060. if (history_handling == Bindings::NavigationHistoryBehavior::Auto) {
  1061. // FIXME: Fix spec typo targetNavigable --> navigable
  1062. // 1. If url equals navigable's active document's URL,
  1063. // and initiatorOriginSnapshot is same origin with targetNavigable's active document's origin,
  1064. // then set historyHandling to "replace".
  1065. if (url.equals(active_document.url(), URL::ExcludeFragment::Yes) && initiator_origin_snapshot.is_same_origin(active_document.origin()))
  1066. history_handling = Bindings::NavigationHistoryBehavior::Replace;
  1067. // 2. Otherwise, set historyHandling to "push".
  1068. else
  1069. history_handling = Bindings::NavigationHistoryBehavior::Push;
  1070. }
  1071. // 10. If the navigation must be a replace given url and navigable's active document, then set historyHandling to "replace".
  1072. if (navigation_must_be_a_replace(url, active_document))
  1073. history_handling = Bindings::NavigationHistoryBehavior::Replace;
  1074. // 11. If all of the following are true:
  1075. // - documentResource is null;
  1076. // - response is null;
  1077. // - url equals navigable's active session history entry's URL with exclude fragments set to true; and
  1078. // - url's fragment is non-null
  1079. if (document_resource.has<Empty>()
  1080. && !response
  1081. && url.equals(active_session_history_entry()->url(), URL::ExcludeFragment::Yes)
  1082. && url.fragment().has_value()) {
  1083. // 1. Navigate to a fragment given navigable, url, historyHandling, userInvolvement, navigationAPIState, and navigationId.
  1084. TRY(navigate_to_a_fragment(url, to_history_handling_behavior(history_handling), user_involvement, navigation_api_state, navigation_id));
  1085. // 2. Return.
  1086. return {};
  1087. }
  1088. // 12. If navigable's parent is non-null, then set navigable's is delaying load events to true.
  1089. if (parent() != nullptr)
  1090. set_delaying_load_events(true);
  1091. // 13. Let targetBrowsingContext be navigable's active browsing context.
  1092. [[maybe_unused]] auto target_browsing_context = active_browsing_context();
  1093. // 14. Let targetSnapshotParams be the result of snapshotting target snapshot params given navigable.
  1094. auto target_snapshot_params = snapshot_target_snapshot_params();
  1095. // FIXME: 15. Invoke WebDriver BiDi navigation started with targetBrowsingContext, and a new WebDriver BiDi navigation status whose id is navigationId, url is url, and status is "pending".
  1096. // 16. If navigable's ongoing navigation is "traversal", then:
  1097. if (ongoing_navigation().has<Traversal>()) {
  1098. // FIXME: 1. Invoke WebDriver BiDi navigation failed with targetBrowsingContext and a new WebDriver BiDi navigation status whose id is navigationId, status is "canceled", and url is url.
  1099. // 2. Return.
  1100. return {};
  1101. }
  1102. // 17. Set navigable's ongoing navigation to navigationId.
  1103. set_ongoing_navigation(navigation_id);
  1104. // 18. If url's scheme is "javascript", then:
  1105. if (url.scheme() == "javascript"sv) {
  1106. // 1. Queue a global task on the navigation and traversal task source given navigable's active window to navigate to a javascript: URL given navigable, url, historyHandling, initiatorOriginSnapshot, and cspNavigationType.
  1107. VERIFY(active_window());
  1108. queue_global_task(Task::Source::NavigationAndTraversal, *active_window(), JS::create_heap_function(heap(), [this, url, history_handling, initiator_origin_snapshot, csp_navigation_type, navigation_id] {
  1109. (void)navigate_to_a_javascript_url(url, to_history_handling_behavior(history_handling), initiator_origin_snapshot, csp_navigation_type, navigation_id);
  1110. }));
  1111. // 2. Return.
  1112. return {};
  1113. }
  1114. // 19. If all of the following are true:
  1115. // - userInvolvement is not "browser UI";
  1116. // - navigable's active document's origin is same origin-domain with sourceDocument's origin;
  1117. // - navigable's active document's is initial about:blank is false; and
  1118. // - url's scheme is a fetch scheme
  1119. // then:
  1120. if (user_involvement != UserNavigationInvolvement::BrowserUI && active_document.origin().is_same_origin_domain(source_document->origin()) && !active_document.is_initial_about_blank() && Fetch::Infrastructure::is_fetch_scheme(url.scheme())) {
  1121. // 1. Let navigation be navigable's active window's navigation API.
  1122. VERIFY(active_window());
  1123. auto navigation = active_window()->navigation();
  1124. // 2. Let entryListForFiring be formDataEntryList if documentResource is a POST resource; otherwise, null.
  1125. auto entry_list_for_firing = [&]() -> Optional<Vector<XHR::FormDataEntry>&> {
  1126. if (document_resource.has<POSTResource>())
  1127. return form_data_entry_list;
  1128. return {};
  1129. }();
  1130. // 3. Let navigationAPIStateForFiring be navigationAPIState if navigationAPIState is not null;
  1131. // otherwise, StructuredSerializeForStorage(undefined).
  1132. auto navigation_api_state_for_firing = navigation_api_state.value_or(MUST(structured_serialize_for_storage(vm, JS::js_undefined())));
  1133. // FIXME: 4. Let continue be the result of firing a push/replace/reload navigate event at navigation
  1134. // with navigationType set to historyHandling, isSameDocument set to false, userInvolvement set to userInvolvement,
  1135. // formDataEntryList set to entryListForFiring, destinationURL set to url, and navigationAPIState set to navigationAPIStateForFiring.
  1136. (void)navigation;
  1137. (void)entry_list_for_firing;
  1138. (void)navigation_api_state_for_firing;
  1139. // FIXME: 5. If continue is false, then return.
  1140. }
  1141. if (is_top_level_traversable()) {
  1142. active_browsing_context()->page().client().page_did_start_loading(url, false);
  1143. }
  1144. // 20. In parallel, run these steps:
  1145. Platform::EventLoopPlugin::the().deferred_invoke([this, source_snapshot_params, target_snapshot_params, csp_navigation_type, document_resource, url, navigation_id, referrer_policy, initiator_origin_snapshot, response, history_handling, initiator_base_url_snapshot] {
  1146. // AD-HOC: Not in the spec but subsequent steps will fail if the navigable doesn't have an active window.
  1147. if (!active_window()) {
  1148. set_delaying_load_events(false);
  1149. return;
  1150. }
  1151. // 1. Let unloadPromptCanceled be the result of checking if unloading is user-canceled for navigable's active document's inclusive descendant navigables.
  1152. auto unload_prompt_canceled = traversable_navigable()->check_if_unloading_is_canceled(this->active_document()->inclusive_descendant_navigables());
  1153. // 2. If unloadPromptCanceled is true, or navigable's ongoing navigation is no longer navigationId, then:
  1154. if (unload_prompt_canceled != TraversableNavigable::CheckIfUnloadingIsCanceledResult::Continue || !ongoing_navigation().has<String>() || ongoing_navigation().get<String>() != navigation_id) {
  1155. // FIXME: 1. Invoke WebDriver BiDi navigation failed with targetBrowsingContext and a new WebDriver BiDi navigation status whose id is navigationId, status is "canceled", and url is url.
  1156. // 2. Abort these steps.
  1157. set_delaying_load_events(false);
  1158. return;
  1159. }
  1160. // AD-HOC: Not in the spec but subsequent steps will fail if the navigable doesn't have an active window.
  1161. if (!active_window()) {
  1162. set_delaying_load_events(false);
  1163. return;
  1164. }
  1165. // 3. Queue a global task on the navigation and traversal task source given navigable's active window to abort a document and its descendants given navigable's active document.
  1166. queue_global_task(Task::Source::NavigationAndTraversal, *active_window(), JS::create_heap_function(heap(), [this] {
  1167. VERIFY(this->active_document());
  1168. this->active_document()->abort_a_document_and_its_descendants();
  1169. }));
  1170. // 4. Let documentState be a new document state with
  1171. // request referrer policy: referrerPolicy
  1172. // initiator origin: initiatorOriginSnapshot
  1173. // resource: documentResource
  1174. // navigable target name: navigable's target name
  1175. JS::NonnullGCPtr<DocumentState> document_state = *heap().allocate_without_realm<DocumentState>();
  1176. document_state->set_request_referrer_policy(referrer_policy);
  1177. document_state->set_initiator_origin(initiator_origin_snapshot);
  1178. document_state->set_resource(document_resource);
  1179. document_state->set_navigable_target_name(target_name());
  1180. // 5. If url matches about:blank or is about:srcdoc, then set documentState's origin to documentState's initiator origin.
  1181. if (url_matches_about_blank(url) || url_matches_about_srcdoc(url)) {
  1182. // document_resource cannot have an Empty if the url is about:srcdoc since we rely on document_resource
  1183. // having a String to call create_navigation_params_from_a_srcdoc_resource
  1184. if (url_matches_about_srcdoc(url) && document_resource.has<Empty>()) {
  1185. document_state->set_resource({ String {} });
  1186. }
  1187. // 1. Set documentState's origin to initiatorOriginSnapshot.
  1188. document_state->set_origin(document_state->initiator_origin());
  1189. // 2. Set documentState's about base URL to initiatorBaseURLSnapshot.
  1190. document_state->set_about_base_url(initiator_base_url_snapshot);
  1191. }
  1192. // 6. Let historyEntry be a new session history entry, with its URL set to url and its document state set to documentState.
  1193. JS::NonnullGCPtr<SessionHistoryEntry> history_entry = *heap().allocate_without_realm<SessionHistoryEntry>();
  1194. history_entry->set_url(url);
  1195. history_entry->set_document_state(document_state);
  1196. // 7. Let navigationParams be null.
  1197. NavigationParamsVariant navigation_params = Empty {};
  1198. // FIXME: 8. If response is non-null:
  1199. if (response) {
  1200. }
  1201. // 9. Attempt to populate the history entry's document
  1202. // for historyEntry, given navigable, "navigate", sourceSnapshotParams,
  1203. // targetSnapshotParams, navigationId, navigationParams, cspNavigationType, with allowPOST
  1204. // set to true and completionSteps set to the following step:
  1205. populate_session_history_entry_document(history_entry, source_snapshot_params, target_snapshot_params, navigation_id, navigation_params, csp_navigation_type, true, JS::create_heap_function(heap(), [this, history_entry, history_handling, navigation_id] {
  1206. // 1. Append session history traversal steps to navigable's traversable to finalize a cross-document navigation given navigable, historyHandling, and historyEntry.
  1207. traversable_navigable()->append_session_history_traversal_steps(JS::create_heap_function(heap(), [this, history_entry, history_handling, navigation_id] {
  1208. if (this->has_been_destroyed()) {
  1209. // NOTE: This check is not in the spec but we should not continue navigation if navigable has been destroyed.
  1210. set_delaying_load_events(false);
  1211. return;
  1212. }
  1213. if (this->ongoing_navigation() != navigation_id) {
  1214. // NOTE: This check is not in the spec but we should not continue navigation if ongoing navigation id has changed.
  1215. set_delaying_load_events(false);
  1216. return;
  1217. }
  1218. finalize_a_cross_document_navigation(*this, to_history_handling_behavior(history_handling), history_entry);
  1219. }));
  1220. })).release_value_but_fixme_should_propagate_errors();
  1221. });
  1222. return {};
  1223. }
  1224. // https://html.spec.whatwg.org/multipage/browsing-the-web.html#navigate-fragid
  1225. WebIDL::ExceptionOr<void> Navigable::navigate_to_a_fragment(URL::URL const& url, HistoryHandlingBehavior history_handling, UserNavigationInvolvement user_involvement, Optional<SerializationRecord> navigation_api_state, String navigation_id)
  1226. {
  1227. (void)navigation_id;
  1228. // 1. Let navigation be navigable's active window's navigation API.
  1229. VERIFY(active_window());
  1230. auto navigation = active_window()->navigation();
  1231. // 2. Let destinationNavigationAPIState be navigable's active session history entry's navigation API state.
  1232. // 3. If navigationAPIState is not null, then set destinationNavigationAPIState to navigationAPIState.
  1233. auto destination_navigation_api_state = navigation_api_state.has_value() ? *navigation_api_state : active_session_history_entry()->navigation_api_state();
  1234. // 4. Let continue be the result of firing a push/replace/reload navigate event at navigation with navigationType set to historyHandling, isSameDocument set to true,
  1235. // userInvolvement set to userInvolvement, and destinationURL set to url, and navigationAPIState set to destinationNavigationAPIState.
  1236. auto navigation_type = history_handling == HistoryHandlingBehavior::Push ? Bindings::NavigationType::Push : Bindings::NavigationType::Replace;
  1237. bool const continue_ = navigation->fire_a_push_replace_reload_navigate_event(navigation_type, url, true, user_involvement, {}, destination_navigation_api_state);
  1238. // 5. If continue is false, then return.
  1239. if (!continue_)
  1240. return {};
  1241. // 6. Let historyEntry be a new session history entry, with
  1242. // URL: url
  1243. // document state: navigable's active session history entry's document state
  1244. // navigation API state: destinationNavigationAPIState
  1245. // scroll restoration mode: navigable's active session history entry's scroll restoration mode
  1246. JS::NonnullGCPtr<SessionHistoryEntry> history_entry = heap().allocate_without_realm<SessionHistoryEntry>();
  1247. history_entry->set_url(url);
  1248. history_entry->set_document_state(active_session_history_entry()->document_state());
  1249. history_entry->set_navigation_api_state(destination_navigation_api_state);
  1250. history_entry->set_scroll_restoration_mode(active_session_history_entry()->scroll_restoration_mode());
  1251. // 7. Let entryToReplace be navigable's active session history entry if historyHandling is "replace", otherwise null.
  1252. auto entry_to_replace = history_handling == HistoryHandlingBehavior::Replace ? active_session_history_entry() : nullptr;
  1253. // 8. Let history be navigable's active document's history object.
  1254. auto history = active_document()->history();
  1255. // 9. Let scriptHistoryIndex be history's index.
  1256. auto script_history_index = history->m_index;
  1257. // 10. Let scriptHistoryLength be history's length.
  1258. auto script_history_length = history->m_length;
  1259. // 11. If historyHandling is "push", then:
  1260. if (history_handling == HistoryHandlingBehavior::Push) {
  1261. // 1. Set history's state to null.
  1262. history->set_state(JS::js_null());
  1263. // 2. Increment scriptHistoryIndex.
  1264. script_history_index++;
  1265. // 3. Set scriptHistoryLength to scriptHistoryIndex + 1.
  1266. script_history_length = script_history_index + 1;
  1267. }
  1268. // 12. Set navigable's active session history entry to historyEntry.
  1269. m_active_session_history_entry = history_entry;
  1270. // 13. Update document for history step application given navigable's active document, historyEntry, true, scriptHistoryIndex, and scriptHistoryLength.
  1271. // AD HOC: Skip updating the navigation api entries twice here
  1272. active_document()->update_for_history_step_application(*history_entry, true, script_history_length, script_history_index, navigation_type, {}, {}, false);
  1273. // 14. Update the navigation API entries for a same-document navigation given navigation, historyEntry, and historyHandling.
  1274. navigation->update_the_navigation_api_entries_for_a_same_document_navigation(history_entry, navigation_type);
  1275. // 15. Scroll to the fragment given navigable's active document.
  1276. // FIXME: Specification doesn't say when document url needs to update during fragment navigation
  1277. active_document()->set_url(url);
  1278. active_document()->scroll_to_the_fragment();
  1279. // 16. Let traversable be navigable's traversable navigable.
  1280. auto traversable = traversable_navigable();
  1281. // 17. Append the following session history synchronous navigation steps involving navigable to traversable:
  1282. traversable->append_session_history_synchronous_navigation_steps(*this, JS::create_heap_function(heap(), [this, traversable, history_entry, entry_to_replace, navigation_id, history_handling] {
  1283. // 1. Finalize a same-document navigation given traversable, navigable, historyEntry, and entryToReplace.
  1284. finalize_a_same_document_navigation(*traversable, *this, history_entry, entry_to_replace, history_handling);
  1285. // FIXME: 2. Invoke WebDriver BiDi fragment navigated with navigable's active browsing context and a new WebDriver BiDi
  1286. // navigation status whose id is navigationId, url is url, and status is "complete".
  1287. (void)navigation_id;
  1288. }));
  1289. return {};
  1290. }
  1291. // https://html.spec.whatwg.org/multipage/browsing-the-web.html#evaluate-a-javascript:-url
  1292. WebIDL::ExceptionOr<JS::GCPtr<DOM::Document>> Navigable::evaluate_javascript_url(URL::URL const& url, URL::Origin const& new_document_origin, String navigation_id)
  1293. {
  1294. auto& vm = this->vm();
  1295. VERIFY(active_window());
  1296. auto& realm = active_window()->realm();
  1297. // 1. Let urlString be the result of running the URL serializer on url.
  1298. auto url_string = url.serialize();
  1299. // 2. Let encodedScriptSource be the result of removing the leading "javascript:" from urlString.
  1300. auto encoded_script_source = url_string.substring_view(11, url_string.length() - 11);
  1301. // 3. Let scriptSource be the UTF-8 decoding of the percent-decoding of encodedScriptSource.
  1302. auto script_source = URL::percent_decode(encoded_script_source);
  1303. // 4. Let settings be targetNavigable's active document's relevant settings object.
  1304. auto& settings = active_document()->relevant_settings_object();
  1305. // 5. Let baseURL be settings's API base URL.
  1306. auto base_url = settings.api_base_url();
  1307. // 6. Let script be the result of creating a classic script given scriptSource, settings, baseURL, and the default classic script fetch options.
  1308. auto script = HTML::ClassicScript::create("(javascript url)", script_source, settings, base_url);
  1309. // 7. Let evaluationStatus be the result of running the classic script script.
  1310. auto evaluation_status = script->run();
  1311. // 8. Let result be null.
  1312. String result;
  1313. // 9. If evaluationStatus is a normal completion, and evaluationStatus.[[Value]] is a String, then set result to evaluationStatus.[[Value]].
  1314. if (evaluation_status.type() == JS::Completion::Type::Normal && evaluation_status.value()->is_string()) {
  1315. result = evaluation_status.value()->as_string().utf8_string();
  1316. } else {
  1317. // 10. Otherwise, return null.
  1318. return nullptr;
  1319. }
  1320. // 11. Let response be a new response with
  1321. // URL: targetNavigable's active document's URL
  1322. // header list: «(`Content-Type`, `text/html;charset=utf-8`)»
  1323. // body: the UTF-8 encoding of result, as a body
  1324. auto response = Fetch::Infrastructure::Response::create(vm);
  1325. response->url_list().append(active_document()->url());
  1326. auto header = Fetch::Infrastructure::Header::from_string_pair("Content-Type"sv, "text/html"sv);
  1327. response->header_list()->append(move(header));
  1328. response->set_body(TRY(Fetch::Infrastructure::byte_sequence_as_body(realm, result.bytes())));
  1329. // 12. Let policyContainer be targetNavigable's active document's policy container.
  1330. auto const& policy_container = active_document()->policy_container();
  1331. // FIXME: 13. Let finalSandboxFlags be policyContainer's CSP list's CSP-derived sandboxing flags.
  1332. auto final_sandbox_flags = SandboxingFlagSet {};
  1333. // 14. Let coop be targetNavigable's active document's opener policy.
  1334. auto const& coop = active_document()->opener_policy();
  1335. // 15. Let coopEnforcementResult be a new opener policy enforcement result with
  1336. // url: url
  1337. // origin: newDocumentOrigin
  1338. // opener policy: coop
  1339. OpenerPolicyEnforcementResult coop_enforcement_result {
  1340. .url = url,
  1341. .origin = new_document_origin,
  1342. .opener_policy = coop,
  1343. };
  1344. // 16. Let navigationParams be a new navigation params, with
  1345. // id: navigationId
  1346. // navigable: targetNavigable
  1347. // request: null
  1348. // response: response
  1349. // fetch controller: null
  1350. // commit early hints: null
  1351. // COOP enforcement result: coopEnforcementResult
  1352. // reserved environment: null
  1353. // origin: newDocumentOrigin
  1354. // policy container: policyContainer
  1355. // final sandboxing flag set: finalSandboxFlags
  1356. // opener policy: coop
  1357. // FIXME: navigation timing type: "navigate"
  1358. // about base URL: targetNavigable's active document's about base URL
  1359. auto navigation_params = vm.heap().allocate_without_realm<NavigationParams>();
  1360. navigation_params->id = navigation_id;
  1361. navigation_params->navigable = this;
  1362. navigation_params->request = {};
  1363. navigation_params->response = response;
  1364. navigation_params->fetch_controller = nullptr;
  1365. navigation_params->commit_early_hints = nullptr;
  1366. navigation_params->coop_enforcement_result = move(coop_enforcement_result);
  1367. navigation_params->reserved_environment = {};
  1368. navigation_params->origin = new_document_origin;
  1369. navigation_params->policy_container = policy_container;
  1370. navigation_params->final_sandboxing_flag_set = final_sandbox_flags;
  1371. navigation_params->opener_policy = coop;
  1372. navigation_params->about_base_url = active_document()->about_base_url();
  1373. // 17. Return the result of loading an HTML document given navigationParams.
  1374. return load_document(navigation_params);
  1375. }
  1376. // https://html.spec.whatwg.org/multipage/browsing-the-web.html#navigate-to-a-javascript:-url
  1377. WebIDL::ExceptionOr<void> Navigable::navigate_to_a_javascript_url(URL::URL const& url, HistoryHandlingBehavior history_handling, URL::Origin const& initiator_origin, CSPNavigationType csp_navigation_type, String navigation_id)
  1378. {
  1379. // 1. Assert: historyHandling is "replace".
  1380. VERIFY(history_handling == HistoryHandlingBehavior::Replace);
  1381. // 2. Set the ongoing navigation for targetNavigable to null.
  1382. set_ongoing_navigation({});
  1383. // 3. If initiatorOrigin is not same origin-domain with targetNavigable's active document's origin, then return.
  1384. if (!initiator_origin.is_same_origin_domain(active_document()->origin()))
  1385. return {};
  1386. // FIXME: 4. Let request be a new request whose URL is url.
  1387. // FIXME: 5. If the result of should navigation request of type be blocked by Content Security Policy? given request and cspNavigationType is "Blocked", then return.
  1388. (void)csp_navigation_type;
  1389. // 6. Let newDocument be the result of evaluating a javascript: URL given targetNavigable, url, and initiatorOrigin.
  1390. auto new_document = TRY(evaluate_javascript_url(url, initiator_origin, navigation_id));
  1391. // 7. If newDocument is null, then return.
  1392. if (!new_document) {
  1393. // NOTE: In this case, some JavaScript code was executed, but no new Document was created, so we will not perform a navigation.
  1394. return {};
  1395. }
  1396. // 8. Assert: initiatorOrigin is newDocument's origin.
  1397. VERIFY(initiator_origin == new_document->origin());
  1398. // 9. Let entryToReplace be targetNavigable's active session history entry.
  1399. auto entry_to_replace = active_session_history_entry();
  1400. // 10. Let oldDocState be entryToReplace's document state.
  1401. auto old_doc_state = entry_to_replace->document_state();
  1402. // 11. Let documentState be a new document state with
  1403. // document: newDocument
  1404. // history policy container: a clone of the oldDocState's history policy container if it is non-null; null otherwise
  1405. // request referrer: oldDocState's request referrer
  1406. // request referrer policy: oldDocState's request referrer policy
  1407. // initiator origin: initiatorOrigin
  1408. // origin: initiatorOrigin
  1409. // about base URL: oldDocState's about base URL
  1410. // resource: null
  1411. // ever populated: true
  1412. // navigable target name: oldDocState's navigable target name
  1413. JS::NonnullGCPtr<DocumentState> document_state = *heap().allocate_without_realm<DocumentState>();
  1414. document_state->set_document(new_document);
  1415. document_state->set_history_policy_container(old_doc_state->history_policy_container());
  1416. document_state->set_request_referrer(old_doc_state->request_referrer());
  1417. document_state->set_request_referrer_policy(old_doc_state->request_referrer_policy());
  1418. document_state->set_initiator_origin(initiator_origin);
  1419. document_state->set_origin(initiator_origin);
  1420. document_state->set_about_base_url(old_doc_state->about_base_url());
  1421. document_state->set_ever_populated(true);
  1422. document_state->set_navigable_target_name(old_doc_state->navigable_target_name());
  1423. // 12. Let historyEntry be a new session history entry, with
  1424. // URL: entryToReplace's URL
  1425. // document state: documentState
  1426. JS::NonnullGCPtr<SessionHistoryEntry> history_entry = *heap().allocate_without_realm<SessionHistoryEntry>();
  1427. history_entry->set_url(entry_to_replace->url());
  1428. history_entry->set_document_state(document_state);
  1429. // 13. Append session history traversal steps to targetNavigable's traversable to finalize a cross-document navigation with targetNavigable, historyHandling, and historyEntry.
  1430. traversable_navigable()->append_session_history_traversal_steps(JS::create_heap_function(heap(), [this, history_entry, history_handling, navigation_id] {
  1431. finalize_a_cross_document_navigation(*this, history_handling, history_entry);
  1432. }));
  1433. return {};
  1434. }
  1435. // https://html.spec.whatwg.org/multipage/browsing-the-web.html#reload
  1436. void Navigable::reload()
  1437. {
  1438. // 1. Set navigable's active session history entry's document state's reload pending to true.
  1439. active_session_history_entry()->document_state()->set_reload_pending(true);
  1440. // 2. Let traversable be navigable's traversable navigable.
  1441. auto traversable = traversable_navigable();
  1442. // 3. Append the following session history traversal steps to traversable:
  1443. traversable->append_session_history_traversal_steps(JS::create_heap_function(heap(), [traversable] {
  1444. // 1. Apply the reload history step to traversable.
  1445. traversable->apply_the_reload_history_step();
  1446. }));
  1447. }
  1448. // https://html.spec.whatwg.org/multipage/browsing-the-web.html#the-navigation-must-be-a-replace
  1449. bool navigation_must_be_a_replace(URL::URL const& url, DOM::Document const& document)
  1450. {
  1451. return url.scheme() == "javascript"sv || document.is_initial_about_blank();
  1452. }
  1453. // https://html.spec.whatwg.org/multipage/browsing-the-web.html#allowed-to-navigate
  1454. bool Navigable::allowed_by_sandboxing_to_navigate(Navigable const& target, SourceSnapshotParams const& source_snapshot_params)
  1455. {
  1456. auto& source = *this;
  1457. auto is_ancestor_of = [](Navigable const& a, Navigable const& b) {
  1458. for (auto parent = b.parent(); parent; parent = parent->parent()) {
  1459. if (parent.ptr() == &a)
  1460. return true;
  1461. }
  1462. return false;
  1463. };
  1464. // A navigable source is allowed by sandboxing to navigate a second navigable target,
  1465. // given a source snapshot params sourceSnapshotParams, if the following steps return true:
  1466. // 1. If source is target, then return true.
  1467. if (&source == &target)
  1468. return true;
  1469. // 2. If source is an ancestor of target, then return true.
  1470. if (is_ancestor_of(source, target))
  1471. return true;
  1472. // 3. If target is an ancestor of source, then:
  1473. if (is_ancestor_of(target, source)) {
  1474. // 1. If target is not a top-level traversable, then return true.
  1475. if (!target.is_top_level_traversable())
  1476. return true;
  1477. // 2. If sourceSnapshotParams's has transient activation is true, and sourceSnapshotParams's sandboxing flags's
  1478. // sandboxed top-level navigation with user activation browsing context flag is set, then return false.
  1479. if (source_snapshot_params.has_transient_activation && has_flag(source_snapshot_params.sandboxing_flags, SandboxingFlagSet::SandboxedTopLevelNavigationWithUserActivation))
  1480. return false;
  1481. // 3. If sourceSnapshotParams's has transient activation is false, and sourceSnapshotParams's sandboxing flags's
  1482. // sandboxed top-level navigation without user activation browsing context flag is set, then return false.
  1483. if (!source_snapshot_params.has_transient_activation && has_flag(source_snapshot_params.sandboxing_flags, SandboxingFlagSet::SandboxedTopLevelNavigationWithoutUserActivation))
  1484. return false;
  1485. // 4. Return true.
  1486. return true;
  1487. }
  1488. // 4. If target is a top-level traversable:
  1489. if (target.is_top_level_traversable()) {
  1490. // FIXME: 1. If source is the one permitted sandboxed navigator of target, then return true.
  1491. // 2. If sourceSnapshotParams's sandboxing flags's sandboxed navigation browsing context flag is set, then return false.
  1492. if (has_flag(source_snapshot_params.sandboxing_flags, SandboxingFlagSet::SandboxedNavigation))
  1493. return false;
  1494. // 3. Return true.
  1495. return true;
  1496. }
  1497. // 5. If sourceSnapshotParams's sandboxing flags's sandboxed navigation browsing context flag is set, then return false.
  1498. // 6. Return true.
  1499. return !has_flag(source_snapshot_params.sandboxing_flags, SandboxingFlagSet::SandboxedNavigation);
  1500. }
  1501. // https://html.spec.whatwg.org/multipage/browsing-the-web.html#snapshotting-target-snapshot-params
  1502. TargetSnapshotParams Navigable::snapshot_target_snapshot_params()
  1503. {
  1504. // To snapshot target snapshot params given a navigable targetNavigable, return a new target snapshot params
  1505. // with sandboxing flags set to the result of determining the creation sandboxing flags given targetNavigable's
  1506. // active browsing context and targetNavigable's container.
  1507. return { determine_the_creation_sandboxing_flags(*active_browsing_context(), container()) };
  1508. }
  1509. // https://html.spec.whatwg.org/multipage/browsing-the-web.html#finalize-a-cross-document-navigation
  1510. void finalize_a_cross_document_navigation(JS::NonnullGCPtr<Navigable> navigable, HistoryHandlingBehavior history_handling, JS::NonnullGCPtr<SessionHistoryEntry> history_entry)
  1511. {
  1512. // NOTE: This is not in the spec but we should not navigate destroyed navigable.
  1513. if (navigable->has_been_destroyed())
  1514. return;
  1515. // 1. FIXME: Assert: this is running on navigable's traversable navigable's session history traversal queue.
  1516. // 2. Set navigable's is delaying load events to false.
  1517. navigable->set_delaying_load_events(false);
  1518. // 3. If historyEntry's document is null, then return.
  1519. if (!history_entry->document())
  1520. return;
  1521. // 4. If all of the following are true:
  1522. // - navigable's parent is null;
  1523. // - historyEntry's document's browsing context is not an auxiliary browsing context whose opener browsing context is non-null; and
  1524. // - historyEntry's document's origin is not navigable's active document's origin
  1525. // then set historyEntry's document state's navigable target name to the empty string.
  1526. if (navigable->parent() == nullptr && history_entry->document()->browsing_context()->opener_browsing_context() != nullptr && history_entry->document()->origin() != navigable->active_document()->origin())
  1527. history_entry->document_state()->set_navigable_target_name(String {});
  1528. // 5. Let entryToReplace be navigable's active session history entry if historyHandling is "replace", otherwise null.
  1529. auto entry_to_replace = history_handling == HistoryHandlingBehavior::Replace ? navigable->active_session_history_entry() : nullptr;
  1530. // 6. Let traversable be navigable's traversable navigable.
  1531. auto traversable = navigable->traversable_navigable();
  1532. // 7. Let targetStep be null.
  1533. int target_step;
  1534. // 8. Let targetEntries be the result of getting session history entries for navigable.
  1535. auto& target_entries = navigable->get_session_history_entries();
  1536. // 9. If entryToReplace is null, then:
  1537. if (entry_to_replace == nullptr) {
  1538. // 1. Clear the forward session history of traversable.
  1539. traversable->clear_the_forward_session_history();
  1540. // 2. Set targetStep to traversable's current session history step + 1.
  1541. target_step = traversable->current_session_history_step() + 1;
  1542. // 3. Set historyEntry's step to targetStep.
  1543. history_entry->set_step(target_step);
  1544. // 4. Append historyEntry to targetEntries.
  1545. target_entries.append(history_entry);
  1546. } else {
  1547. // 1. Replace entryToReplace with historyEntry in targetEntries.
  1548. *(target_entries.find(*entry_to_replace)) = history_entry;
  1549. // 2. Set historyEntry's step to entryToReplace's step.
  1550. history_entry->set_step(entry_to_replace->step());
  1551. // 3. If historyEntry's document state's origin is same origin with entryToReplace's document state's origin,
  1552. // then set historyEntry's navigation API key to entryToReplace's navigation API key.
  1553. if (history_entry->document_state()->origin().has_value() && entry_to_replace->document_state()->origin().has_value() && history_entry->document_state()->origin()->is_same_origin(*entry_to_replace->document_state()->origin())) {
  1554. history_entry->set_navigation_api_key(entry_to_replace->navigation_api_key());
  1555. }
  1556. // 4. Set targetStep to traversable's current session history step.
  1557. target_step = traversable->current_session_history_step();
  1558. }
  1559. // 10. Apply the push/replace history step targetStep to traversable.
  1560. traversable->apply_the_push_or_replace_history_step(target_step, history_handling, TraversableNavigable::SynchronousNavigation::No);
  1561. }
  1562. // https://html.spec.whatwg.org/multipage/browsing-the-web.html#url-and-history-update-steps
  1563. void perform_url_and_history_update_steps(DOM::Document& document, URL::URL new_url, Optional<SerializationRecord> serialized_data, HistoryHandlingBehavior history_handling)
  1564. {
  1565. // 1. Let navigable be document's node navigable.
  1566. auto navigable = document.navigable();
  1567. // 2. Let activeEntry be navigable's active session history entry.
  1568. auto active_entry = navigable->active_session_history_entry();
  1569. // FIXME: Spec should be updated to say "classic history api state" instead of serialized state
  1570. // 3. Let newEntry be a new session history entry, with
  1571. // URL: newURL
  1572. // serialized state: if serializedData is not null, serializedData; otherwise activeEntry's classic history API state
  1573. // document state: activeEntry's document state
  1574. // scroll restoration mode: activeEntry's scroll restoration mode
  1575. // FIXME: persisted user state: activeEntry's persisted user state
  1576. JS::NonnullGCPtr<SessionHistoryEntry> new_entry = document.heap().allocate_without_realm<SessionHistoryEntry>();
  1577. new_entry->set_url(new_url);
  1578. new_entry->set_classic_history_api_state(serialized_data.value_or(active_entry->classic_history_api_state()));
  1579. new_entry->set_document_state(active_entry->document_state());
  1580. new_entry->set_scroll_restoration_mode(active_entry->scroll_restoration_mode());
  1581. // 4. If document's is initial about:blank is true, then set historyHandling to "replace".
  1582. if (document.is_initial_about_blank()) {
  1583. history_handling = HistoryHandlingBehavior::Replace;
  1584. }
  1585. // 5. Let entryToReplace be activeEntry if historyHandling is "replace", otherwise null.
  1586. auto entry_to_replace = history_handling == HistoryHandlingBehavior::Replace ? active_entry : nullptr;
  1587. // 6. If historyHandling is "push", then:
  1588. if (history_handling == HistoryHandlingBehavior::Push) {
  1589. // 1. Increment document's history object's index.
  1590. document.history()->m_index++;
  1591. // 2. Set document's history object's length to its index + 1.
  1592. document.history()->m_length = document.history()->m_index + 1;
  1593. }
  1594. // If serializedData is not null, then restore the history object state given document and newEntry.
  1595. if (serialized_data.has_value())
  1596. document.restore_the_history_object_state(new_entry);
  1597. // 8. Set document's URL to newURL.
  1598. document.set_url(new_url);
  1599. // 9. Set document's latest entry to newEntry.
  1600. document.set_latest_entry(new_entry);
  1601. // 10. Set navigable's active session history entry to newEntry.
  1602. navigable->set_active_session_history_entry(new_entry);
  1603. // 11. Update the navigation API entries for a same-document navigation given document's relevant global object's navigation API, newEntry, and historyHandling.
  1604. auto& relevant_global_object = verify_cast<Window>(HTML::relevant_global_object(document));
  1605. auto navigation_type = history_handling == HistoryHandlingBehavior::Push ? Bindings::NavigationType::Push : Bindings::NavigationType::Replace;
  1606. relevant_global_object.navigation()->update_the_navigation_api_entries_for_a_same_document_navigation(new_entry, navigation_type);
  1607. // 12. Let traversable be navigable's traversable navigable.
  1608. auto traversable = navigable->traversable_navigable();
  1609. // 13. Append the following session history synchronous navigation steps involving navigable to traversable:
  1610. traversable->append_session_history_synchronous_navigation_steps(*navigable, JS::create_heap_function(document.realm().heap(), [traversable, navigable, new_entry, entry_to_replace, history_handling] {
  1611. // 1. Finalize a same-document navigation given traversable, navigable, newEntry, and entryToReplace.
  1612. finalize_a_same_document_navigation(*traversable, *navigable, new_entry, entry_to_replace, history_handling);
  1613. }));
  1614. }
  1615. void Navigable::scroll_offset_did_change()
  1616. {
  1617. // https://w3c.github.io/csswg-drafts/cssom-view-1/#scrolling-events
  1618. // Whenever a viewport gets scrolled (whether in response to user interaction or by an API), the user agent must run these steps:
  1619. // 1. Let doc be the viewport’s associated Document.
  1620. auto doc = active_document();
  1621. VERIFY(doc);
  1622. // 2. If doc is already in doc’s pending scroll event targets, abort these steps.
  1623. for (auto& target : doc->pending_scroll_event_targets()) {
  1624. if (target.ptr() == doc)
  1625. return;
  1626. }
  1627. // 3. Append doc to doc’s pending scroll event targets.
  1628. doc->pending_scroll_event_targets().append(*doc);
  1629. }
  1630. CSSPixelRect Navigable::to_top_level_rect(CSSPixelRect const& a_rect)
  1631. {
  1632. auto rect = a_rect;
  1633. rect.set_location(to_top_level_position(a_rect.location()));
  1634. return rect;
  1635. }
  1636. CSSPixelPoint Navigable::to_top_level_position(CSSPixelPoint a_position)
  1637. {
  1638. auto position = a_position;
  1639. for (auto ancestor = parent(); ancestor; ancestor = ancestor->parent()) {
  1640. if (is<TraversableNavigable>(*ancestor))
  1641. break;
  1642. if (!ancestor->container())
  1643. return {};
  1644. if (!ancestor->container()->paintable())
  1645. return {};
  1646. position.translate_by(ancestor->container()->paintable()->box_type_agnostic_position());
  1647. }
  1648. return position;
  1649. }
  1650. void Navigable::set_viewport_size(CSSPixelSize size)
  1651. {
  1652. if (m_size == size)
  1653. return;
  1654. m_size = size;
  1655. if (auto document = active_document()) {
  1656. // NOTE: Resizing the viewport changes the reference value for viewport-relative CSS lengths.
  1657. document->invalidate_style(DOM::StyleInvalidationReason::NavigableSetViewportSize);
  1658. document->set_needs_layout();
  1659. }
  1660. if (auto document = active_document()) {
  1661. document->set_needs_display(InvalidateDisplayList::No);
  1662. document->inform_all_viewport_clients_about_the_current_viewport_rect();
  1663. // Schedule the HTML event loop to ensure that a `resize` event gets fired.
  1664. HTML::main_thread_event_loop().schedule();
  1665. }
  1666. }
  1667. void Navigable::perform_scroll_of_viewport(CSSPixelPoint new_position)
  1668. {
  1669. if (m_viewport_scroll_offset != new_position) {
  1670. m_viewport_scroll_offset = new_position;
  1671. scroll_offset_did_change();
  1672. if (auto document = active_document()) {
  1673. document->set_needs_display(InvalidateDisplayList::No);
  1674. document->set_needs_to_refresh_scroll_state(true);
  1675. document->inform_all_viewport_clients_about_the_current_viewport_rect();
  1676. }
  1677. }
  1678. // Schedule the HTML event loop to ensure that a `resize` event gets fired.
  1679. HTML::main_thread_event_loop().schedule();
  1680. }
  1681. void Navigable::set_needs_display(InvalidateDisplayList should_invalidate_display_list)
  1682. {
  1683. if (auto document = active_document(); document) {
  1684. document->set_needs_display(should_invalidate_display_list);
  1685. }
  1686. }
  1687. // https://html.spec.whatwg.org/#rendering-opportunity
  1688. bool Navigable::has_a_rendering_opportunity() const
  1689. {
  1690. // A navigable has a rendering opportunity if the user agent is currently able to present
  1691. // the contents of the navigable to the user,
  1692. // accounting for hardware refresh rate constraints and user agent throttling for performance reasons,
  1693. // but considering content presentable even if it's outside the viewport.
  1694. // A navigable has no rendering opportunities if its active document is render-blocked
  1695. // or if it is suppressed for view transitions;
  1696. // otherwise, rendering opportunities are determined based on hardware constraints
  1697. // such as display refresh rates and other factors such as page performance
  1698. // or whether the document's visibility state is "visible".
  1699. // Rendering opportunities typically occur at regular intervals.
  1700. // FIXME: Return `false` here if we're an inactive browser tab.
  1701. auto browsing_context = const_cast<Navigable*>(this)->active_browsing_context();
  1702. if (!browsing_context)
  1703. return false;
  1704. return browsing_context->page().client().is_ready_to_paint();
  1705. }
  1706. // https://html.spec.whatwg.org/multipage/nav-history-apis.html#inform-the-navigation-api-about-aborting-navigation
  1707. void Navigable::inform_the_navigation_api_about_aborting_navigation()
  1708. {
  1709. // FIXME: 1. If this algorithm is running on navigable's active window's relevant agent's event loop, then continue on to the following steps.
  1710. // Otherwise, queue a global task on the navigation and traversal task source given navigable's active window to run the following steps.
  1711. // AD-HOC: Not in the spec but subsequent steps will fail if the navigable doesn't have an active window.
  1712. if (!active_window())
  1713. return;
  1714. queue_global_task(Task::Source::NavigationAndTraversal, *active_window(), JS::create_heap_function(heap(), [this] {
  1715. // 2. Let navigation be navigable's active window's navigation API.
  1716. VERIFY(active_window());
  1717. auto navigation = active_window()->navigation();
  1718. // 3. If navigation's ongoing navigate event is null, then return.
  1719. if (navigation->ongoing_navigate_event() == nullptr)
  1720. return;
  1721. // 4. Abort the ongoing navigation given navigation.
  1722. navigation->abort_the_ongoing_navigation();
  1723. }));
  1724. }
  1725. // https://html.spec.whatwg.org/multipage/browsing-the-web.html#event-uni
  1726. UserNavigationInvolvement user_navigation_involvement(DOM::Event const& event)
  1727. {
  1728. // For convenience at certain call sites, the user navigation involvement for an Event event is defined as follows:
  1729. // 1. Assert: this algorithm is being called as part of an activation behavior definition.
  1730. // 2. Assert: event's type is "click".
  1731. VERIFY(event.type() == "click"_fly_string);
  1732. // 3. If event's isTrusted is initialized to true, then return "activation".
  1733. // 4. Return "none".
  1734. return event.is_trusted() ? UserNavigationInvolvement::Activation : UserNavigationInvolvement::None;
  1735. }
  1736. bool Navigable::is_focused() const
  1737. {
  1738. return &m_page->focused_navigable() == this;
  1739. }
  1740. static String visible_text_in_range(DOM::Range const& range)
  1741. {
  1742. // NOTE: This is an adaption of Range stringification, but we skip over DOM nodes that don't have a corresponding layout node.
  1743. StringBuilder builder;
  1744. if (range.start_container() == range.end_container() && is<DOM::Text>(*range.start_container())) {
  1745. if (!range.start_container()->layout_node())
  1746. return String {};
  1747. return MUST(static_cast<DOM::Text const&>(*range.start_container()).data().substring_from_byte_offset(range.start_offset(), range.end_offset() - range.start_offset()));
  1748. }
  1749. if (is<DOM::Text>(*range.start_container()) && range.start_container()->layout_node())
  1750. builder.append(static_cast<DOM::Text const&>(*range.start_container()).data().bytes_as_string_view().substring_view(range.start_offset()));
  1751. for (DOM::Node const* node = range.start_container(); node != range.end_container()->next_sibling(); node = node->next_in_pre_order()) {
  1752. if (is<DOM::Text>(*node) && range.contains_node(*node) && node->layout_node())
  1753. builder.append(static_cast<DOM::Text const&>(*node).data());
  1754. }
  1755. if (is<DOM::Text>(*range.end_container()) && range.end_container()->layout_node())
  1756. builder.append(static_cast<DOM::Text const&>(*range.end_container()).data().bytes_as_string_view().substring_view(0, range.end_offset()));
  1757. return MUST(builder.to_string());
  1758. }
  1759. String Navigable::selected_text() const
  1760. {
  1761. auto document = const_cast<Navigable*>(this)->active_document();
  1762. if (!document)
  1763. return String {};
  1764. auto selection = const_cast<DOM::Document&>(*document).get_selection();
  1765. auto range = selection->range();
  1766. if (!range)
  1767. return String {};
  1768. return visible_text_in_range(*range);
  1769. }
  1770. void Navigable::select_all()
  1771. {
  1772. auto document = active_document();
  1773. if (!document)
  1774. return;
  1775. auto selection = document->get_selection();
  1776. if (!selection)
  1777. return;
  1778. if (auto position = document->cursor_position(); position && position->node()->is_editable()) {
  1779. auto& node = *position->node();
  1780. auto node_length = node.length();
  1781. (void)selection->set_base_and_extent(node, 0, node, node_length);
  1782. document->set_cursor_position(DOM::Position::create(document->realm(), node, node_length));
  1783. } else if (auto* body = document->body()) {
  1784. (void)selection->select_all_children(*body);
  1785. }
  1786. }
  1787. void Navigable::paste(String const& text)
  1788. {
  1789. auto document = active_document();
  1790. if (!document)
  1791. return;
  1792. m_event_handler.handle_paste(text);
  1793. }
  1794. }