1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233# 1 "src/lib/eliom_client.client.ml"(* Ocsigen
* http://www.ocsigen.org
* Copyright (C) 2010 Vincent Balat
* Copyright (C) 2011 Jérôme Vouillon, Grégoire Henry, Pierre Chambart
* Copyright (C) 2012 Benedikt Becker
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, with linking exception;
* either version 2.1 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*)letsection=Eliom_client_core.sectionopenJs_of_ocamlopenEliom_libmoduleOpt=Eliom_lib.OptionmoduleXml=Eliom_content_core.Xml(* == Callbacks for onload, onbeforeunload, and onunload *)letrun_callbackshandlers=List.iter(funf->f())handlerstypechangepage_event={in_cache:bool;origin_uri:string;target_uri:string;origin_id:int;target_id:intoption}letrun_lwt_callbacks:'a->('a->unitLwt.t)list->unitLwt.t=funevhandlers->Lwt_list.iter_s(funh->hev)handlerslet(onload,_,flush_onload,_push_onload):((unit->unit)->unit)*(unit->(unit->unit)list)*(unit->(unit->unit)list)*(unit->unit)=Eliom_client_core.create_buffer()let((onchangepage:(changepage_event->unitLwt.t)->unit),_,(flush_onchangepage:unit->(changepage_event->unitLwt.t)list),_)=Eliom_client_core.create_buffer()letonunload,_,flush_onunload,_=Eliom_client_core.create_buffer()letonbeforeunload,run_onbeforeunload,flush_onbeforeunload=letadd,get,flush,_=Eliom_client_core.create_buffer()inletrecrunlst=matchlstwith|[]->None|f::rem->(matchf()withNone->runrem|Somes->Somes)inadd,(fun()->run(get())),flushletrun_onunload_wrapperset_contentcancel=matchrun_onbeforeunload()with|Someswhennot(confirm"%s"s)->cancel()|_->ignore(flush_onbeforeunload());run_callbacks(flush_onunload());set_content()letlwt_onload()=lett,u=Lwt.wait()inonload(Lwt.wakeupu);t(* == Initialize the client values sent with a request *)letcheck_global_dataglobal_data=letmissing_client_values=ref[]inletmissing_injections=ref[]inString_map.iter(funcompilation_unit_id{Eliom_client_core.server_section;client_section}->List.iter(fundata->missing_client_values:=List.rev_append(List.map(funcv->compilation_unit_id,cv)(Array.to_listdata))!missing_client_values)server_section;List.iter(fundata->missing_injections:=List.rev_append(Array.to_listdata)!missing_injections)client_section)global_data;(match!missing_client_valueswith|[]->()|l->Printf.ksprintf(funs->Firebug.console##(error(Js.strings)))"Code generating the following client values is not linked on the client:\n%s"(String.concat"\n"(List.rev_map(fun(compilation_unit_id,{Eliom_runtime.closure_id;value;_})->letinstance_id=Eliom_runtime.Client_value_server_repr.instance_idvalueinmatchEliom_runtime.Client_value_server_repr.locvaluewith|None->Printf.sprintf"%s:%s/%d"compilation_unit_idclosure_idinstance_id|Somepos->Printf.sprintf"%s:%s/%d at %s"compilation_unit_idclosure_idinstance_id(Eliom_lib.pos_to_stringpos))l)));match!missing_injectionswith|[]->()|l->Printf.ksprintf(funs->Firebug.console##(error(Js.strings)))"Code containing the following injections is not linked on the client:\n%s"(String.concat"\n"(List.rev_map(fund->letid=d.Eliom_runtime.injection_idinmatchd.Eliom_runtime.injection_dbgwith|None->Printf.sprintf"%d"id|Some(pos,Somei)->Printf.sprintf"%d (%s at %s)"idi(Eliom_lib.pos_to_stringpos)|Some(pos,None)->Printf.sprintf"%d (at %s)"id(Eliom_lib.pos_to_stringpos))l))letdo_request_datarequest_data=Lwt_log.ign_debug_f~section"Do request data (%a)"(fun()l->string_of_int(Array.lengthl))request_data;(* On a request, i.e. after running the toplevel definitions, global_data
must contain at most empty sections_data lists, which stem from server-
only eliom files. *)check_global_data!Eliom_client_core.global_data;Eliom_client_core.global_data:=String_map.empty;Array.iterEliom_client_core.Client_value.initializerequest_data(* == Relink
Traverse the Dom representation of the page in order to register
"unique" nodes (or substitute previously known global nodes) and to
bind Eliom's event handlers.
*)letget_element_cookies_infoelt=Js.Opt.to_option(Js.Opt.mapelt##(getAttribute(Js.stringEliom_runtime.RawXML.ce_call_service_attrib))(funs->of_json(Js.to_strings)))letget_element_templateelt=Js.Opt.to_option(Js.Opt.mapelt##(getAttribute(Js.stringEliom_runtime.RawXML.ce_template_attrib))(funs->Js.to_strings))leta_handler=Dom_html.full_handler(funnodeev->letnode=Js.Opt.get(Dom_html.CoerceTo.anode)(fun()->Lwt_log.raise_error_f~section"not an anchor element")in(* We prevent default behaviour
only if raw_a_handler has taken the change page itself *)(*VVV Better: use preventdefault rather than returning false *)Js.bool(Eliom_client_core.raw_a_handlernode(get_element_cookies_infonode)(get_element_templatenode)ev))letform_handler:(Dom_html.elementJs.t,#Dom_html.eventJs.t)Dom_html.event_listener=Dom_html.full_handler(funnodeev->letform=Js.Opt.get(Dom_html.CoerceTo.formnode)(fun()->Lwt_log.raise_error_f~section"not a form element")inletkind=ifString.lowercase_ascii(Js.to_stringform##._method)="get"then`Form_getelse`Form_postandf_=Lwt.return_falseinJs.bool(Eliom_client_core.raw_form_handlerformkind(get_element_cookies_infoform)(get_element_templatenode)evf))letrelink_process_node(node:Dom_html.elementJs.t)=letid=Js.Opt.getnode##(getAttribute(Js.stringEliom_runtime.RawXML.node_id_attrib))(fun()->Lwt_log.raise_error_f~section"unique node without id attribute")inJs.Optdef.case(Eliom_client_core.find_process_nodeid)(fun()->Lwt_log.ign_debug_f~section"Relink process node: did not find %a. Will add it."(fun()->Js.to_string)id;Eliom_client_core.register_process_nodeid(node:>Dom.nodeJs.t))(funpnode->Lwt_log.ign_debug_f~section"Relink process node: found %a"(fun()->Js.to_string)id;Js.Opt.iternode##.parentNode(funparent->Dom.replaceChildparentpnodenode);ifString.sub(Js.to_bytestringid)07<>"global_"then(letchildrens=Dom.list_of_nodeListpnode##.childNodesinList.iter(func->ignorepnode##(removeChildc))childrens;letchildrens=Dom.list_of_nodeListnode##.childNodesinList.iter(func->ignorepnode##(appendChildc))childrens))letrelink_request_node(node:Dom_html.elementJs.t)=letid=Js.Opt.getnode##(getAttribute(Js.stringEliom_runtime.RawXML.node_id_attrib))(fun()->Lwt_log.raise_error_f~section"unique node without id attribute")inJs.Optdef.case(Eliom_client_core.find_request_nodeid)(fun()->Lwt_log.ign_debug_f~section"Relink request node: did not find %a. Will add it."(fun()->Js.to_string)id;Eliom_client_core.register_request_nodeid(node:>Dom.nodeJs.t))(funpnode->Lwt_log.ign_debug_f~section"Relink request node: found %a"(fun()->Js.to_string)id;Js.Opt.iternode##.parentNode(funparent->Dom.replaceChildparentpnodenode))letrelink_request_nodesroot=Lwt_log.ign_debug~section"Relink request nodes";if!Eliom_config.debug_timingsthenFirebug.console##(time(Js.string"relink_request_nodes"));Eliommod_dom.iter_nodeList(Eliommod_dom.select_request_nodesroot)relink_request_node;if!Eliom_config.debug_timingsthenFirebug.console##(timeEnd(Js.string"relink_request_nodes"))(* Relinks a-elements, form-elements, and process nodes. The list of
closure nodes is returned for application on [relink_closure_node]
after the client values are initialized.
*)letrelink_page_but_client_values(root:Dom_html.elementJs.t)=Lwt_log.ign_debug~section"Relink page";let(a_nodeList,form_nodeList,process_nodeList,closure_nodeList,attrib_nodeList)=Eliommod_dom.select_nodesrootinEliommod_dom.iter_nodeLista_nodeList(funnode->node##.onclick:=a_handler);Eliommod_dom.iter_nodeListform_nodeList(funnode->node##.onsubmit:=form_handler);Eliommod_dom.iter_nodeListprocess_nodeListrelink_process_node;closure_nodeList,attrib_nodeList(* == Rebuild event handlers
Event handlers inside the DOM tree are rebuilt from the closure map
sent with the request. The actual functions will be taken from the
client values.
It returns a single handler ([unit -> unit]) which captures all
onload event handlers found in the tree, and cancels the execution
when on raises [False] (cf. [raw_event_handler]).
*)letis_closure_attrib,get_closure_name,get_closure_id=letv_prefix=Eliom_runtime.RawXML.closure_attr_prefixinletv_len=String.lengthv_prefixinletv_prefix_js=Js.stringv_prefixinletn_prefix=Eliom_runtime.RawXML.closure_name_prefixinletn_len=String.lengthn_prefixinletn_prefix_js=Js.stringn_prefixin((funattr->attr##.value##(substring0v_len)=v_prefix_js&&attr##.name##(substring0n_len)=n_prefix_js),(funattr->attr##.name##(substring_toEndn_len)),funattr->attr##.value##(substring_toEndv_len))letrelink_closure_noderootonloadtable(node:Dom_html.elementJs.t)=Lwt_log.ign_debug~section"Relink closure node";letauxattr=ifis_closure_attribattrthenletcid=Js.to_bytestring(get_closure_idattr)inletname=get_closure_nameattrintryletcv=Eliom_runtime.RawXML.ClosureMap.findcidtableinletclosure=Eliom_client_core.raw_event_handlercvinifname=Js.string"onload"then(ifEliommod_dom.ancessorrootnode(* if not inside a unique node replaced by an older one *)thenonload:=closure::!onload)elseJs.Unsafe.setnodename(Dom_html.handler(funev->Js.bool(closureev)))withNot_found->Lwt_log.ign_error_f~section"relink_closure_node: client value %s not found"cidinEliommod_dom.iter_attrListnode##.attributesauxletrelink_closure_nodes(root:Dom_html.elementJs.t)event_handlersclosure_nodeList=Lwt_log.ign_debug_f~section"Relink %i closure nodes"closure_nodeList##.length;letonload=ref[]inEliommod_dom.iter_nodeListclosure_nodeList(funnode->relink_closure_noderootonloadevent_handlersnode);fun()->letev=Eliommod_dom.createEvent(Js.string"load")inignore(List.for_all(funf->fev)(List.rev!onload))letis_attrib_attrib,get_attrib_id=letv_prefix=Eliom_runtime.RawXML.client_attr_prefixinletv_len=String.lengthv_prefixinletv_prefix_js=Js.stringv_prefixinletn_prefix=Eliom_runtime.RawXML.client_name_prefixinletn_len=String.lengthn_prefixinletn_prefix_js=Js.stringn_prefixin((funattr->attr##.value##(substring0v_len)=v_prefix_js&&attr##.name##(substring0n_len)=n_prefix_js),funattr->attr##.value##(substring_toEndv_len))letrelink_attrib_roottable(node:Dom_html.elementJs.t)=Lwt_log.ign_debug~section"Relink attribute";letauxattr=ifis_attrib_attribattrthenletcid=Js.to_bytestring(get_attrib_idattr)intryletvalue=Eliom_runtime.RawXML.ClosureMap.findcidtableinletrattrib:Eliom_content_core.Xml.attrib=Eliom_lib.from_poly(Eliom_lib.to_polyvalue)inEliom_client_core.rebuild_rattribnoderattribwithNot_found->Lwt_log.raise_error_f~section"relink_attrib: client value %s not found"cidinEliommod_dom.iter_attrListnode##.attributesauxletrelink_attribs(root:Dom_html.elementJs.t)attribsattrib_nodeList=Lwt_log.ign_debug_f~section"Relink %i attributes"attrib_nodeList##.length;Eliommod_dom.iter_nodeListattrib_nodeList(funnode->relink_attribrootattribsnode)(* == Extract the request data and the request tab-cookies from a page
See the corresponding function on the server side:
Eliom_registration.Eliom_appl_reg_make_param.make_eliom_data_script.
*)letload_data_scriptpage=Lwt_log.ign_debug~section"Load Eliom application data";lethead=Eliommod_dom.get_headpageinletdata_script:Dom_html.scriptElementJs.t=matchDom.list_of_nodeListhead##.childNodeswith|_::_::data_script::_->(letdata_script:Dom.elementJs.t=Js.Unsafe.coercedata_scriptinmatchJs.to_bytestringdata_script##.tagName##toLowerCasewith|"script"->Js.Unsafe.coercedata_script|t->Lwt_log.raise_error_f~section"Unable to find Eliom application data (script element expected, found %s element)"t)|_->Lwt_log.raise_error_f~section"Unable to find Eliom application data."inletscript=data_script##.textinif!Eliom_config.debug_timingsthenFirebug.console##(time(Js.string"load_data_script"));ignore(Js.Unsafe.eval_string(Js.to_stringscript));Eliom_process.reset_request_template();Eliom_process.reset_request_cookies();if!Eliom_config.debug_timingsthenFirebug.console##(timeEnd(Js.string"load_data_script"))(* == Scroll the current page such that the top of element with the id
[fragment] is aligned with the window's top. If the optional
argument [?offset] is given, ignore the fragment and scroll to the
given offset. *)letscroll_to_fragment?offsetfragment=matchoffsetwith|Somepos->Eliommod_dom.setDocumentScrollpos|None->(matchfragmentwith|None|Some""->Eliommod_dom.setDocumentScrollEliommod_dom.top_position|Somefragment->letscroll_to_elemente=e##(scrollIntoViewJs._true)inletelem=Dom_html.document##(getElementById(Js.stringfragment))inJs.Opt.iterelemscroll_to_element)letwith_progress_cursor:'aLwt.t->'aLwt.t=funt->try%lwtDom_html.document##.body##.style##.cursor:=Js.string"progress";let%lwtres=tinDom_html.document##.body##.style##.cursor:=Js.string"auto";Lwt.returnreswithexn->Dom_html.document##.body##.style##.cursor:=Js.string"auto";Lwt.failexn(* Type for partially unwrapped elt. *)typetmp_recontent=(* arguments ('econtent') are already unwrapped. *)|RELazyofXml.econtentEliom_lazy.request|REofXml.econtent[@@warning"-37"]typetmp_elt={(* to be unwrapped *)tmp_elt:tmp_recontent;tmp_node_id:Xml.node_id}(******************************************************************************)(* Register unwrappers *)(* == Html elements
Html elements are unwrapped lazily (cf. use of Xml.make_lazy in
unwrap_tyxml), because the unwrapping of process and request
elements needs access to the DOM.
All recently unwrapped elements are forced when resetting the
request nodes ([reset_request_nodes]).
*)letunwrap_tyxmltmp_elt=letelt=matchtmp_elt.tmp_eltwith|RELazyelt->Eliom_lazy.forceelt|REelt->eltinLwt_log.ign_debug~section"Unwrap tyxml";(* Do not rebuild dom node while unwrapping, otherwise we
don't have control on when "onload" event handlers are
triggered. *)letelt=letcontext="unwrapping (i.e. utilize it in whatsoever form)"inXml.make_lazy~id:tmp_elt.tmp_node_id(lazy(matchtmp_elt.tmp_node_idwith|Xml.ProcessIdprocess_idasid->Lwt_log.ign_debug_f~section"Unwrap tyxml from ProcessId %s"process_id;Js.Optdef.case(Eliom_client_core.find_process_node(Js.bytestringprocess_id))(fun()->Lwt_log.ign_debug~section"not found";letxml_elt:Xml.elt=Xml.make~ideltinletxml_elt=Eliom_content_core.Xml.set_classes_of_eltxml_eltinEliom_client_core.register_process_node(Js.bytestringprocess_id)(Eliom_client_core.rebuild_node_ns`HTML5contextxml_elt);xml_elt)(funelt->Lwt_log.ign_debug~section"found";Xml.make_dom~idelt)|Xml.RequestIdrequest_idasid->Lwt_log.ign_debug_f~section"Unwrap tyxml from RequestId %s"request_id;Js.Optdef.case(Eliom_client_core.find_request_node(Js.bytestringrequest_id))(fun()->Lwt_log.ign_debug~section"not found";letxml_elt:Xml.elt=Xml.make~ideltinEliom_client_core.register_request_node(Js.bytestringrequest_id)(Eliom_client_core.rebuild_node_ns`HTML5contextxml_elt);xml_elt)(funelt->Lwt_log.ign_debug~section"found";Xml.make_dom~idelt)|Xml.NoIdasid->Lwt_log.ign_debug~section"Unwrap tyxml from NoId";Xml.make~idelt))inEliom_client_core.register_unwrapped_eltelt;eltletunwrap_client_valuecv=Eliom_client_core.Client_value.find~instance_id:(Eliom_runtime.Client_value_server_repr.instance_idcv)(* BB By returning [None] this value will be registered for late
unwrapping, and late unwrapped in Client_value.initialize as
soon as it is available. *)letunwrap_global_data(global_data',_)=Eliom_client_core.global_data:=String_map.map(fun{Eliom_runtime.server_sections_data;client_sections_data}->{Eliom_client_core.server_section=Array.to_listserver_sections_data;client_section=Array.to_listclient_sections_data})global_data'let_=Eliom_unwrap.register_unwrapper'(Eliom_unwrap.id_of_intEliom_common_base.client_value_unwrap_id_int)unwrap_client_value;Eliom_unwrap.register_unwrapper(Eliom_unwrap.id_of_intEliom_runtime.tyxml_unwrap_id_int)unwrap_tyxml;Eliom_unwrap.register_unwrapper(Eliom_unwrap.id_of_intEliom_common_base.global_data_unwrap_id_int)unwrap_global_data;()letadd_string_event_listeneroefcapt:unit=lete=Js.stringeandcapt=Js.boolcaptandfe=matchfewith|Somes->lets=Js.stringsin(Js.Unsafe.coercee)##.returnValue:=s;Js.defs|None->Js.undefinedinletf=Js.Unsafe.callbackfinignore@@ifnot(Js.Optdef.test(Js.Unsafe.coerceo)##.addEventListener)thenlete=(Js.string"on")##(concate)andcbe=Js.Unsafe.call(f,e,[||])in(Js.Unsafe.coerceo)##(attachEventecb)else(Js.Unsafe.coerceo)##(addEventListenerefcapt)(* == Associate data to state of the History API.
We store an 'id' in the state, and store data in an association
table in the session storage. This allows avoiding "replaceState"
that has not a coherent behaviour between Chromium and Firefox
(2012/03).
Storing the scroll position in the state is not required with
Chrome or Firefox: they automatically store and restore the
correct scrolling while browsing the history. However this
behaviour in not required by the HTML5 specification (only
suggested). *)typestate={(* TODO store cookies_info in state... *)template:stringoption;position:Eliommod_dom.position}letrandom_int=ifJs.Optdef.testJs.Unsafe.global##.crypto&&Js.Optdef.testJs.Unsafe.global##.crypto##.getRandomValuesthenfun()->Typed_array.unsafe_getJs.Unsafe.global##.crypto##(getRandomValues(new%jsTyped_array.int32Array1))0elsefun()->truncate(4294967296.*.Js.math##random)letsection_page=Lwt_log.Section.make"eliom:client:page"typestate_id={session_id:int;state_index:int(* point in history *)}modulePage_status_t=structtypet=Generating|Active|Cached|Deadletto_stringst=matchstwith|Generating->"Generating"|Active->"Active"|Cached->"Cached"|Dead->"Dead"endtypepage={page_unique_id:int;mutablepage_id:state_id;mutableurl:string;page_status:Page_status_t.tReact.S.t;mutableprevious_page:intoption;set_page_status:?step:React.step->Page_status_t.t->unit;mutabledom:Dom_html.bodyElementJs.toption;mutablereload_function:(unit->unit->Eliom_service.resultLwt.t)option}letstring_of_pagep=Printf.sprintf"%d/%d %s %s %d %b"p.page_unique_idp.page_id.state_indexp.url(Page_status_t.to_string@@React.S.valuep.page_status)(matchp.previous_pagewithSomepp->pp|None->0)(matchp.domwithSome_->true|None->false)letset_page_statuspst=Lwt_log.ign_debug_f~section:section_page"Set page status %d/%d: %s"p.page_unique_idp.page_id.state_index(Page_status_t.to_stringst);p.set_page_statusstletretire_pagep=set_page_statusp@@matchp.domwithSome_->Cached|None->Deadletsession_id=random_int()letnext_state_id=letlast=ref0infun()->incrlast;{session_id;state_index=!last}letlast_page_id=ref(-1)letmk_page?(state_id=next_state_id())?url?previous_page~status()=incrlast_page_id;Lwt_log.ign_debug_f~section:section_page"Create page %d/%d"!last_page_idstate_id.state_index;letpage_status,set_page_status=React.S.createstatusin(* protect page_status from React.S.stop ~strong:true *)ignore@@React.S.map(fun_->())page_status;{page_unique_id=!last_page_id;page_id=state_id;url=(matchurlwith|Someu->u|None->fst(Url.split_fragment(Js.to_stringDom_html.window##.location##.href)));page_status;previous_page;set_page_status;dom=None;reload_function=None}letactive_page=ref@@mk_page~status:Active()letset_active_pagep=Lwt_log.ign_debug_f~section:section_page"Set active page %d/%d"p.page_unique_idp.page_id.state_index;retire_page!active_page;active_page:=p;set_page_status!active_pageActive(* This key serves as a hook to access the page the currently running code is
generating. *)letthis_page:pageLwt.key=Lwt.new_key()letget_this_page()=matchLwt.getthis_pagewith|Somep->p|None->Lwt_log.ign_debug_f~section:section_page"No page in context";!active_pageletwith_new_page?state_id?old_page~replace()f=letstate_id=ifreplacethenSome!active_page.page_idelsestate_idinleturl,previous_page=matchold_pagewith|Someo->Someo.url,o.previous_page|None->None,Noneinletpage=mk_page?state_id?url?previous_page~status:Generating()inLwt.with_valuethis_page(Somepage)fmoduleHistory=structletsection=Lwt_log.Section.make"eliom:client:history"letget,set=lethistory=ref[!active_page]inletseth=Lwt_log.ign_debug_f~section"setting history:\n%s"(String.concat"\n"@@List.mapstring_of_page!history);history:=hin(fun()->!history),setletfind_by_state_indexi=trySome(List.find(funp->p.page_id.state_index=i)(get()))withNot_found->Noneletsplit_rev_past_futureindex=letreclooppast=function|[]->past,[]|x::futurewhenx.page_id.state_index=index->x::past,future|x::l->loop(x::past)linloop[](get())letadvancen=letnew_history,future=matchn.previous_pagewith|None->get(),[]|Somepp->letrev_past,future=split_rev_past_futureppinList.rev(n::rev_past),futureinList.iter(funp->set_page_statuspDead)future;setnew_historyletreplacen=letmaybe_replacep=ifp.page_id.state_index=n.page_id.state_indexthen(set_page_statuspDead;n)elsepinset@@List.mapmaybe_replace@@get()letpast()=letindex=!active_page.page_id.state_indexinletrev_past,_=split_rev_past_futureindexinList.map(funp->p.url)@@matchrev_pastwith_present::past->past|[]->[]letfuture()=letindex=!active_page.page_id.state_indexinlet_,future=split_rev_past_futureindexinList.map(funp->p.url)futureletmax_num_doms=refNoneletgarbage_collect_doms()=match!max_num_domswith|None->()|Somemax_num_doms->letinterleavelr=lettake_from_l=reffalseinletalternate__=take_from_l:=not!take_from_l;if!take_from_lthen-1else1inList.mergealternatelrinletrev_past,future=split_rev_past_future!active_page.page_id.state_indexinletpages_ordered_by_distance_from_present=interleaverev_pastfutureinletnum_doms=ref0inletmaybe_delete_domp=matchp.domwith|None->()|Some_->num_doms:=!num_doms+1;if!num_doms>max_num_domsthen(p.dom<-None;set_page_statuspDead)inList.itermaybe_delete_dompages_ordered_by_distance_from_presentendletadvance_page()=letnew_page=get_this_page()inifnew_page!=!active_pagethen(new_page.previous_page<-Some!active_page.page_id.state_index;(matchHistory.find_by_state_indexnew_page.page_id.state_indexwith|Some_->()|None->History.advancenew_page);set_active_pagenew_page)letstate_key{session_id;state_index}=Js.string(Printf.sprintf"state_history_%x_%x"session_idstate_index)letget_statestate_id:state=Js.Opt.case(Js.Optdef.caseDom_html.window##.sessionStorage(fun()->(* We use this only when the history API is
available. Sessionstorage seems to be available
everywhere the history API exists. *)Lwt_log.raise_error_f~section"sessionStorage not available")(funs->s##(getItem(state_keystate_id))))(fun()->raiseNot_found)(funs->Json.unsafe_inputs)letset_statei(v:state)=Js.Optdef.caseDom_html.window##.sessionStorage(fun()->())(funs->s##(setItem(state_keyi)(Json.outputv)))letupdate_state()=set_state!active_page.page_id{template=Eliom_request_info.get_request_template();position=Eliommod_dom.getDocumentScroll()}letlock_request_handling=Eliom_request.lockletunlock_request_handling=Eliom_request.unlocktype('a,+'b)server_function='a->'bLwt.tletonly_replace_body=reffalseletpersist_document_head()=only_replace_body:=true(*
Cordova does not allow to read from a file when using the WkWebview.
So, CSS preloading does not work. This provide a work-around.
Also, with Chrome, the corresponding XHRs will block if other requests
have been scheduled before, even when the CSS is cached. This can slow
down page changes.
*)letinsert_basepage=letb=Dom_html.createBaseDom_html.documentinb##.href:=Js.string(Eliom_process.get_base_url());b##.id:=Js.stringEliom_common_base.base_elt_id;Js.Opt.casepage##(querySelector(Js.string"head"))(fun()->Lwt_log.ign_debug_f"No <head> found in document")(funhead->Dom.appendChildheadb)letget_global_data()=letdef()=Noneandid=Js.string"__global_data"inJs.Optdef.caseDom_html.window##.localStoragedef@@funstorage->Js.Opt.casestorage##(getItemid)def@@funv->Lwt_log.ign_debug_f"Unwrap __global_data";matchEliom_unwrap.unwrap(Url.decode(Js.to_stringv))0with|{Eliom_runtime.ecs_data=`Successv;_}->Lwt_log.ign_debug_f"Unwrap __global_data success";Somev|_->Noneletnormalize_app_pathp=(* remove "" from beginning and end of path *)letp=Eliom_lib.Url.split_pathpinletp=matchpwith""::p->p|_->pinmatchList.revpwith""::p->List.revp|_->pletinit_client_app~app_name?(ssl=false)~hostname?(port=80)~site_dir()=Lwt_log.ign_debug_f"Eliom_client.init_client_app called.";Eliom_process.appl_name_r:=Someapp_name;Eliom_request_info.client_app_initialised:=true;(* For site_dir, we want no trailing slash. We tend to concatenate
it with relative paths, or treat it as a prefix to be removed
from other paths. The trailing slash would be burdensome.
In contrast, we do need the trailing slash in
cpi_original_full_path, because we do have the trailing slash in
page URLs., Hence the site_dir @ [""] below. *)Eliom_process.set_sitedata{Eliom_types.site_dir;site_dir_string=String.concat"/"site_dir};Eliom_process.set_info{Eliom_common.cpi_ssl=ssl;cpi_hostname=hostname;cpi_server_port=port;cpi_original_full_path=site_dir@[""]};Eliom_process.set_request_templateNone;(* We set the tab cookie table, with the app name inside: *)Eliom_process.set_request_cookies(Ocsigen_cookie_map.add~path:[]Eliom_common.appl_name_cookie_name(Ocsigen_cookie_map.OSet(None,app_name,false))Ocsigen_cookie_map.empty);ignore(get_global_data())letis_client_app()=!Eliom_common.is_client_applet_=Eliom_common.is_client_app:=(* Testing if variable __eliom_appl_process_info exists: *)not(Js.Optdef.testJs.Unsafe.global##.___eliom_appl_process_info_foo)letonunload_fun_=update_state();run_callbacks(flush_onunload());Js._trueletonbeforeunload_fun_=run_onbeforeunload()letset_base_url()=Eliom_process.set_base_url(String.concat""[Js.to_stringDom_html.window##.location##.protocol;"//";Js.to_stringDom_html.window##.location##.host;Js.to_stringDom_html.window##.location##.pathname])letdom_history_ready=reffalse(* Function called (in Eliom_client_main), once when starting the app.
Either when sent by a server or initiated on client side.
For client apps, we read __eliom_server, __eliom_app_name,
__eliom_app_path JS variables set by the client app (via the HTML
file loading us).
- __eliom_server : remote Eliom server to contact
- __eliom_app_name : application name
- __eliom_app_path : path app is under. We use this path for calls to
server functions (see Eliom_uri). *)letinit()=(* Initialize client app if the __eliom_server variable is defined *)(ifis_client_app()&&Js.Optdef.testJs.Unsafe.global##.___eliom_server_&&Js.Optdef.testJs.Unsafe.global##.___eliom_app_name_thenletapp_name=Js.to_stringJs.Unsafe.global##.___eliom_app_name_andsite_dir=Js.Optdef.caseJs.Unsafe.global##.___eliom_path_(fun()->[])(funp->normalize_app_path(Js.to_stringp))inmatchUrl.url_of_string(Js.to_stringJs.Unsafe.global##.___eliom_server_)with|Some(Http{hu_host;hu_port;_})->init_client_app~app_name~ssl:false~hostname:hu_host~port:hu_port~site_dir()|Some(Https{hu_host;hu_port;_})->init_client_app~app_name~ssl:true~hostname:hu_host~port:hu_port~site_dir()|_->());letjs_data=lazy(Eliom_request_info.get_request_data())inJs.Optdef.caseJs.Unsafe.global##.___eliom_global_data_(fun()->(* Global data are in [js_data], so we unmarshal it right away. *)ignore(Lazy.forcejs_data))(funglobal_data->(* Global data are in a separate file. We should not unmarshal
[js_data] right away but only once the client program has
been initialized. *)ignore(Eliom_unwrap.unwrap_jsglobal_data);Js.Unsafe.deleteJs.Unsafe.global"__eliom_global_data");(* <base> *)(* The first time we load the page, we record the initial URL in a client
side ref, in order to set <base> (on client-side) in header for each
pages. *)set_base_url();insert_baseDom_html.document;(* </base> *)(* Decoding tab cookies.
2016-03 This was done at the beginning of onload below
but this makes it impossible to use cookies
during initialisation phase. I move this here. -- Vincent *)Eliommod_cookies.update_cookie_table(Some(Eliom_process.get_info()).cpi_hostname)(Eliom_request_info.get_request_cookies());letonload_handler=refNoneinletonload_ev=letjs_data=Lazy.forcejs_datainLwt_log.ign_debug~section"onload (client main)";(match!onload_handlerwith|Someh->Dom.removeEventListenerh;onload_handler:=None|None->());Eliom_client_core.set_initial_load();Lwt.async(fun()->if!Eliom_config.debug_timingsthenFirebug.console##(time(Js.string"onload"));let%lwt()=Eliom_request_info.set_session_info~uri:(String.concat"/"(Eliom_request_info.get_csp_original_full_path()))js_data.Eliom_common.ejs_sess_info@@fun()->Lwt.return_unitin(* Give the browser the chance to actually display the page NOW *)let%lwt()=Js_of_ocaml_lwt.Lwt_js.sleep0.001in(* Ordering matters. See [Eliom_client.set_content] for explanations *)relink_request_nodesDom_html.document##.documentElement;letroot=Dom_html.document##.documentElementinletclosure_nodeList,attrib_nodeList=relink_page_but_client_valuesrootindo_request_datajs_data.Eliom_common.ejs_request_data;(* XXX One should check that all values have been unwrapped.
In fact, client values should be special and all other values
should be eagerly unwrapped. *)let()=relink_attribsrootjs_data.Eliom_common.ejs_client_attrib_tableattrib_nodeListinletonload_closure_nodes=relink_closure_nodesrootjs_data.Eliom_common.ejs_event_handler_tableclosure_nodeListinEliom_client_core.reset_request_nodes();Eliommod_dom.add_formdata_hack_onclick_handler();ifnot(is_client_app())thendom_history_ready:=true;letload_callbacks=flush_onload()@[onload_closure_nodes;Eliom_client_core.broadcast_load_end]inLwt_mutex.unlockEliom_client_core.load_mutex;run_callbacksload_callbacks;if!Eliom_config.debug_timingsthenFirebug.console##(timeEnd(Js.string"onload"));Lwt.return_unit);Js._falseinLwt_log.ign_debug~section"Set load/onload events";onload_handler:=Some(Dom.addEventListenerDom_html.window(Dom.Event.make"load")(Dom.handleronload)Js._true);add_string_event_listenerDom_html.window"beforeunload"onbeforeunload_funfalse;ignore(Dom.addEventListenerDom_html.window(Dom.Event.make"unload")(Dom_html.handleronunload_fun)Js._false)(* == Low-level: call service. *)letcreate_request__?absolute?absolute_path?https(typem)~(service:(_,_,m,_,_,_,_,_,_,_,_)Eliom_service.t)?hostname?port?fragment?keep_nl_params?nl_params?keep_get_na_paramsget_paramspost_params=letpath,get_params,fragment,post_params=Eliom_uri.make_post_uri_components__?absolute?absolute_path?https~service?hostname?port?fragment?keep_nl_params?nl_params?keep_get_na_paramsget_paramspost_paramsinleturi=Eliom_uri.make_string_uri_from_components(path,get_params,fragment)inuri,get_params,post_paramsletcreate_request_(typem)?absolute?absolute_path?https~(service:(_,_,m,_,_,_,_,_,_,_,_)Eliom_service.t)?hostname?port?fragment?keep_nl_params?nl_params?keep_get_na_paramsget_paramspost_params=(* TODO: allow get_get_or_post service to return also the service
with the correct subtype. Then do use Eliom_uri.make_string_uri
and Eliom_uri.make_post_uri_components instead of
Eliom_uri.make_string_uri_ and
Eliom_uri.make_post_uri_components__ *)matchEliom_service.which_methservicewith|Eliom_service.Get'->let((_,get_params,_)ascomponents)=Eliom_uri.make_uri_components?absolute?absolute_path?https~service?hostname?port?fragment?keep_nl_params?nl_paramsget_paramsinleturi=Eliom_uri.make_string_uri_from_componentscomponentsin`Get(uri,get_params)|Eliom_service.Post'->`Post(create_request__?absolute?absolute_path?https~service?hostname?port?fragment?keep_nl_params?nl_params?keep_get_na_paramsget_paramspost_params)|Eliom_service.Put'->`Put(create_request__?absolute?absolute_path?https~service?hostname?port?fragment?keep_nl_params?nl_params?keep_get_na_paramsget_paramspost_params)|Eliom_service.Delete'->`Delete(create_request__?absolute?absolute_path?https~service?hostname?port?fragment?keep_nl_params?nl_params?keep_get_na_paramsget_paramspost_params)letraw_call_service?absolute?absolute_path?https~service?hostname?port?fragment?keep_nl_params?nl_params?keep_get_na_params?progress?upload_progress?override_mime_typeget_paramspost_params=(* with_credentials = true is necessary for client side apps when
we want the Eliom server to be different from the server for
static files (if any). For example when testing a mobile app
in a browser, with Cordova's Web server.
Also set with_credentials to true in CORS configuration.
*)letwith_credentials=not(Eliom_service.is_externalservice)inlet%lwturi,content=matchcreate_request_?absolute?absolute_path?https~service?hostname?port?fragment?keep_nl_params?nl_params?keep_get_na_paramsget_paramspost_paramswith|`Get(uri,_)->Eliom_request.http_get~with_credentials?cookies_info:(Eliom_uri.make_cookies_info(https,service))uri[]?progress?upload_progress?override_mime_typeEliom_request.string_result|`Post(uri,_,post_params)->Eliom_request.http_post~with_credentials?cookies_info:(Eliom_uri.make_cookies_info(https,service))?progress?upload_progress?override_mime_typeuripost_paramsEliom_request.string_result|`Put(uri,_,post_params)->Eliom_request.http_put~with_credentials?cookies_info:(Eliom_uri.make_cookies_info(https,service))?progress?upload_progress?override_mime_typeuripost_paramsEliom_request.string_result|`Delete(uri,_,post_params)->Eliom_request.http_delete~with_credentials?cookies_info:(Eliom_uri.make_cookies_info(https,service))?progress?upload_progress?override_mime_typeuripost_paramsEliom_request.string_resultinmatchcontentwith|None->Lwt.fail(Eliom_request.Failed_request204)|Somecontent->Lwt.return(uri,content)letcall_service?absolute?absolute_path?https~service?hostname?port?fragment?keep_nl_params?nl_params?keep_get_na_params?progress?upload_progress?override_mime_typeget_paramspost_params=let%lwt_,content=raw_call_service?absolute?absolute_path?https~service?hostname?port?fragment?keep_nl_params?nl_params?keep_get_na_params?progress?upload_progress?override_mime_typeget_paramspost_paramsinLwt.returncontent(* == Leave an application. *)letexit_to?window_name?window_features?absolute?absolute_path?https~service?hostname?port?fragment?keep_nl_params?nl_params?keep_get_na_paramsget_paramspost_params=matchcreate_request_?absolute?absolute_path?https~service?hostname?port?fragment?keep_nl_params?nl_params?keep_get_na_paramsget_paramspost_paramswith|`Get(uri,_)->Eliom_request.redirect_get?window_name?window_featuresuri|`Post(uri,_,post_params)->Eliom_request.redirect_post?window_nameuripost_params|`Put(uri,_,post_params)->Eliom_request.redirect_put?window_nameuripost_params|`Delete(uri,_,post_params)->Eliom_request.redirect_delete?window_nameuripost_paramsletwindow_open~window_name?window_features?absolute?absolute_path?https~service?hostname?port?fragment?keep_nl_params?nl_params?keep_get_na_paramsget_params=matchcreate_request_?absolute?absolute_path?https~service?hostname?port?fragment?keep_nl_params?nl_params?keep_get_na_paramsget_params()with|`Get(uri,_)->Dom_html.window##(open_(Js.stringuri)window_name(Js.Opt.optionwindow_features))|`Post(_,_,_)->assertfalse|`Put(_,_,_)->assertfalse|`Delete(_,_,_)->assertfalse(* == Call caml service.
Unwrap the data and execute the associated onload event
handlers.
*)letunwrap_caml_contentcontent=letr:'aEliom_runtime.eliom_caml_service_data=Eliom_unwrap.unwrap(Url.decodecontent)0inLwt.return(r.Eliom_runtime.ecs_data,r.Eliom_runtime.ecs_request_data)letcall_ocaml_service?absolute?absolute_path?https~service?hostname?port?fragment?keep_nl_params?nl_params?keep_get_na_params?progress?upload_progress?override_mime_typeget_paramspost_params=Lwt_log.ign_debug~section"Call OCaml service";let%lwt_,content=raw_call_service?absolute?absolute_path?https~service?hostname?port?fragment?keep_nl_params?nl_params?keep_get_na_params?progress?upload_progress?override_mime_typeget_paramspost_paramsinlet%lwt()=Lwt_mutex.lockEliom_client_core.load_mutexinEliom_client_core.set_loading_phase();let%lwtcontent,request_data=unwrap_caml_contentcontentindo_request_datarequest_data;Eliom_client_core.reset_request_nodes();letload_callbacks=[Eliom_client_core.broadcast_load_end]inLwt_mutex.unlockEliom_client_core.load_mutex;run_callbacksload_callbacks;matchcontentwith|`Successresult->Lwt.returnresult|`Failuremsg->Lwt.fail(Eliom_client_value.Exception_on_servermsg)(* == Current uri.
This reference is used in [change_page_uri] and popstate event
handler to mimic browser's behaviour with fragment: we do not make
any request to the server, if only the fragment part of url
changes.
*)letpath_and_args_of_uriuri=letpath_of_strings=matchUrl.path_of_path_stringswith"."::path->path|path->pathinmatchUrl.url_of_stringuriwith|Some(Url.Httpurl|Url.Httpsurl)->url.Url.hu_path,url.Url.hu_arguments|_->(matchtrySome(String.indexuri'?')withNot_found->Nonewith|Somen->(path_of_stringString.(suburi0n),Url.decode_argumentsString.(suburi(n+1)(lengthuri-n-1)))|None->path_of_stringuri,[])letset_current_uri,get_current_uri=letset_current_uriuri=letcurrent_uri=fst(Url.split_fragmenturi)in(get_this_page()).url<-current_uri;letpath,all_get_params=path_and_args_of_uricurrent_uriinLwt.async@@fun()->Eliom_request_info.update_session_info~path~all_get_params~all_post_params:None(fun()->Lwt.return_unit)inletget_current_uri()=(get_this_page()).urlinset_current_uri,get_current_uri(* == Function [change_url_string] changes the URL, without doing a request.
It uses the History API if present, otherwise we write the new URL
in the fragment part of the URL (see 'redirection_script' in
'server/eliom_registration.ml'). *)letcurrent_pseudo_fragment=ref""leturl_fragment_prefix="!"leturl_fragment_prefix_with_sharp="#!"letreload_function=refNoneletset_reload_functionf=reload_function:=Somefletset_max_dist_history_domslimit=History.max_num_doms:=limit;History.garbage_collect_doms()letpush_history_dom()=if!dom_history_readythen(letpage=!active_pageinletdom=if!only_replace_bodythenDom_html.document##.bodyelseDom_html.document##.documentElementinpage.dom<-Somedom;History.garbage_collect_doms())modulePage_status=structincludePage_status_tletsignal()=letp=get_this_page()inp.page_statusmoduleEvents=structletchanges()=React.S.changes(signal())letactive()=changes()|>React.E.fmap@@functionActive->Some()|_->Noneletcached()=changes()|>React.E.fmap@@functionCached->Some()|_->Noneletdead()=changes()|>React.E.fmap@@functionDead->Some()|_->Noneletinactive()=React.E.select[cached();dead()]endletmaybe_just_once~oncee=ifoncethenReact.E.onceeelseeletstop_event?(stop=React.E.never)e=Dom_reference.retain_generic(get_this_page())~keep:e;Dom_reference.retain_generice~keep:(React.E.map(fun()->React.E.stop~strong:truee)stop)letonactive?(now=true)?(once=false)?stopaction=leton_event()=stop_event?stop@@React.E.mapaction@@maybe_just_once~once@@Events.active()inifnow&&React.S.value(signal())=Activethen(action();ifnotoncethenon_event())elseon_event()letoncached?(once=false)?stopaction=stop_event?stop@@React.E.mapaction@@maybe_just_once~once@@Events.cached()letondead?stopaction=stop_event?stop@@React.E.mapaction(Events.dead())letoninactive?(once=false)?stopaction=stop_event?stop@@React.E.mapaction@@maybe_just_once~once@@Events.inactive()letwhile_active?now?(stop=React.E.never)action=letthread=refLwt.return_unitinonactive?now~stop(fun()->thread:=action());oninactive~stop(fun()->Lwt.cancel!thread);Dom_reference.retain_generic(get_this_page())~keep:(React.E.map(fun()->Lwt.cancel!thread)stop)endletis_in_cachestate_id=matchHistory.find_by_state_indexstate_id.state_indexwith|Some{dom=Some_;_}->true|_->falseletstash_reload_functionf=letpage=get_this_page()inletstate_id=page.page_idinletid=state_id.state_indexinLwt_log.ign_debug_f~section:section_page"Update reload function for page %d"id;page.reload_function<-Somefletchange_url_string~replaceuri=Lwt_log.ign_debug_f~section:section_page"Change url string: %s"uri;letfull_uri=if!Eliom_common.is_client_appthenurielseUrl.resolveuriinset_current_urifull_uri;ifEliom_process.history_apithen(letthis_page=get_this_page()inifreplacethen(Opt.iterstash_reload_function!reload_function;Dom_html.window##.history##replaceState(Js.Opt.return(this_page.page_id,Js.stringfull_uri))(Js.string"")(if!Eliom_common.is_client_appthenJs.nullelseJs.Opt.return(Js.stringuri)))else(update_state();Opt.iterstash_reload_function!reload_function;Dom_html.window##.history##pushState(Js.Opt.return(this_page.page_id,Js.stringfull_uri))(Js.string"")(if!Eliom_common.is_client_appthenJs.nullelseJs.Opt.return(Js.stringuri)));Eliommod_dom.touch_base())else(current_pseudo_fragment:=url_fragment_prefix_with_sharp^uri;ifuri<>fst(Url.split_fragmentUrl.Current.as_string)thenDom_html.window##.location##.hash:=Js.string(url_fragment_prefix^uri))(* == Function [change_url] changes the URL, without doing a request.
It takes a GET (co-)service as parameter and its parameters.
*)letchange_url?(replace=false)?absolute?absolute_path?https~service?hostname?port?fragment?keep_nl_params?nl_paramsparams=Lwt_log.ign_debug~section:section_page"Change url";(reload_function:=matchEliom_service.xhr_with_cookiesservicewith|Nonewhen(https=Sometrue&¬Eliom_request_info.ssl_)||(https=Somefalse&&Eliom_request_info.ssl_)->None|Some(Some_ast)whent=Eliom_request_info.get_request_template()->None|_->(matchEliom_service.reload_funservicewith|Somerf->Some(fun()()->rfparams())|None->None));change_url_string~replace(Eliom_uri.make_string_uri?absolute?absolute_path?https~service?hostname?port?fragment?keep_nl_params?nl_paramsparams)letset_template_content~replace~uri?fragment=letreally_setcontent()=reload_function:=None;(matchfragmentwith|None->change_url_string~replaceuri|Somefragment->change_url_string~replace(uri^"#"^fragment));let%lwt()=Lwt_mutex.lockEliom_client_core.load_mutexinlet%lwt(),request_data=unwrap_caml_contentcontentindo_request_datarequest_data;Eliom_client_core.reset_request_nodes();letload_callbacks=flush_onload()inLwt_mutex.unlockEliom_client_core.load_mutex;run_callbacksload_callbacks;Lwt.return_unitandcancel()=Lwt.return_unitinfunction|None->Lwt.return_unit|Somecontent->run_onunload_wrapper(really_setcontent)cancelletset_uri~replace?fragmenturi=(* Changing url: *)matchfragmentwith|None->change_url_string~replaceuri|Somefragment->change_url_string~replace(uri^"#"^fragment)letreplace_page~do_insert_basenew_page=if!Eliom_config.debug_timingsthenFirebug.console##(time(Js.string"replace_page"));if!only_replace_bodythenletnew_body=new_page##.childNodes##(item1)inJs.Opt.iternew_body(funnew_body->Dom.replaceChildDom_html.document##.documentElementnew_bodyDom_html.document##.body)else((* We insert <base> in the page.
The URLs of all other pages will be computed w.r.t.
the base URL. *)ifdo_insert_basetheninsert_basenew_page;Dom.replaceChildDom_html.documentnew_pageDom_html.document##.documentElement);if!Eliom_config.debug_timingsthenFirebug.console##(timeEnd(Js.string"replace_page"))(* Function to be called for client side services: *)letset_content_local?offset?fragmentnew_page=Lwt_log.ign_debug~section:section_page"Set content local";letlocked=reftrueinletrecover()=if!lockedthenLwt_mutex.unlockEliom_client_core.load_mutex;if!Eliom_config.debug_timingsthenFirebug.console##(timeEnd(Js.string"set_content_local"))andreally_set()=(* Inline CSS in the header to avoid the "flashing effect".
Otherwise, the browser start to display the page before
loading the CSS. *)letpreloaded_css=if!only_replace_bodythenLwt.return_unitelseEliommod_dom.preload_cssnew_pagein(* Wait for CSS to be inlined before substituting global nodes: *)let%lwt()=preloaded_cssin(* Really change page contents *)replace_page~do_insert_base:truenew_page;Eliommod_dom.add_formdata_hack_onclick_handler();dom_history_ready:=true;letload_callbacks=flush_onload()@[Eliom_client_core.broadcast_load_end]inlocked:=false;Lwt_mutex.unlockEliom_client_core.load_mutex;(* run callbacks upon page activation (or now), but just once *)Page_status.onactive~once:true(fun()->run_callbacksload_callbacks);scroll_to_fragment?offsetfragment;advance_page();if!Eliom_config.debug_timingsthenFirebug.console##(timeEnd(Js.string"set_content_local"));Lwt.return_unitinletcancel()=recover();Lwt.return_unitintry%lwtlet%lwt()=Lwt_mutex.lockEliom_client_core.load_mutexinEliom_client_core.set_loading_phase();if!Eliom_config.debug_timingsthenFirebug.console##(time(Js.string"set_content_local"));run_onunload_wrapperreally_setcancelwithexn->recover();Lwt_log.ign_debug~section~exn"set_content_local";Lwt.failexn(* Function to be called for server side services: *)letset_content~replace~uri?offset?fragmentcontent=Lwt_log.ign_debug~section:section_page"Set content";(* TODO: too early? *)lettarget_uri=uriinlet%lwt()=run_lwt_callbacks{in_cache=is_in_cache!active_page.page_id;origin_uri=get_current_uri();target_uri;origin_id=!active_page.page_id.state_index;target_id=None}(flush_onchangepage())inmatchcontentwith|None->Lwt.return_unit|Somecontent->(letlocked=reftrueinletreally_set()=reload_function:=None;set_uri~replace?fragmenturi;(* Convert the DOM nodes from XML elements to HTML elements. *)letfake_page=Eliommod_dom.html_documentcontentEliom_client_core.registered_process_nodein(* insert_base fake_page; Now done server side *)(* Inline CSS in the header to avoid the "flashing effect".
Otherwise, the browser start to display the page before
loading the CSS. *)letpreloaded_css=if!only_replace_bodythenLwt.return_unitelseEliommod_dom.preload_cssfake_pagein(* Unique nodes of scope request must be bound before the
unmarshalling/unwrapping of page data. *)relink_request_nodesfake_page;(* Put the loaded data script in action *)load_data_scriptfake_page;(* Unmarshall page data. *)letcookies=Eliom_request_info.get_request_cookies()inletjs_data=Eliom_request_info.get_request_data()in(* Update tab-cookies: *)lethost=matchUrl.url_of_stringuriwith|Some(Url.Httpurl)|Some(Url.Httpsurl)->Someurl.Url.hu_host|_->NoneinEliommod_cookies.update_cookie_tablehostcookies;(* Wait for CSS to be inlined before substituting global nodes: *)let%lwt()=preloaded_cssin(* Bind unique node (request and global) and register event
handler. Relinking closure nodes must take place after
initializing the client values *)letclosure_nodeList,attrib_nodeList=relink_page_but_client_valuesfake_pageinEliom_request_info.set_session_info~urijs_data.Eliom_common.ejs_sess_info@@fun()->(* Really change page contents *)replace_page~do_insert_base:falsefake_page;(* Initialize and provide client values. May need to access to
new DOM. Necessary for relinking closure nodes *)do_request_datajs_data.Eliom_common.ejs_request_data;(* Replace closure ids in document with event handlers
(from client values) *)let()=relink_attribsDom_html.document##.documentElementjs_data.Eliom_common.ejs_client_attrib_tableattrib_nodeListinletonload_closure_nodes=relink_closure_nodesDom_html.document##.documentElementjs_data.Eliom_common.ejs_event_handler_tableclosure_nodeListin(* The request node table must be empty when nodes received via
call_ocaml_service are unwrapped. *)Eliom_client_core.reset_request_nodes();Eliommod_dom.add_formdata_hack_onclick_handler();dom_history_ready:=true;locked:=false;letload_callbacks=flush_onload()@[onload_closure_nodes;Eliom_client_core.broadcast_load_end]inLwt_mutex.unlockEliom_client_core.load_mutex;run_callbacksload_callbacks;scroll_to_fragment?offsetfragment;advance_page();if!Eliom_config.debug_timingsthenFirebug.console##(timeEnd(Js.string"set_content"));Lwt.return_unitandrecover()=if!lockedthenLwt_mutex.unlockEliom_client_core.load_mutex;if!Eliom_config.debug_timingsthenFirebug.console##(timeEnd(Js.string"set_content"))intry%lwtlet%lwt()=Lwt_mutex.lockEliom_client_core.load_mutexinEliom_client_core.set_loading_phase();if!Eliom_config.debug_timingsthenFirebug.console##(time(Js.string"set_content"));letg()=recover();Lwt.return_unitinrun_onunload_wrapperreally_setgwithexn->recover();Lwt_log.ign_debug~section~exn"set_content";Lwt.failexn)letocamlify_params=List.map(functionv,`Strings->v,Js.to_strings|_,_->assertfalse)letmake_urisubpathparams=letbase=ifis_client_app()thenmatchsubpathwith_::_->String.concat"/"subpath|[]->"/"elseletpath=matchsubpathwith_::_->String.concat"/"subpath|[]->""andport=matchUrl.Current.portwith|Someport->Printf.sprintf":%d"port|None->""inPrintf.sprintf"%s//%s%s/%s"Url.Current.protocolUrl.Current.hostportpathandparams=List.map(fun(s,s')->s,`String(Js.strings'))paramsinEliom_uri.make_string_uri_from_components(base,params,None)letroute({Eliom_route.i_subpath;i_get_params;i_post_params;_}asinfo)=Lwt_log.ign_debug~section:section_page"Route";letinfo,i_subpath=matchi_subpathwith|[".";""]->{infowithi_subpath=[]},[]|i_subpath->info,i_subpathinleturi=make_urii_subpathi_get_paramsinEliom_request_info.update_session_info~path:i_subpath~all_get_params:i_get_params~all_post_params:(Somei_post_params)@@fun()->let%lwtresult=Eliom_route.call_service{infowithEliom_route.i_get_params=Eliom_common.(remove_prefixed_paramnl_param_prefix)i_get_params}inLwt.return(uri,result)letswitch_to_https()=letinfo=Eliom_process.get_info()inEliom_process.set_info{infowithEliom_common.cpi_ssl=true}letstring_of_resultresult=matchresultwith|Eliom_service.No_contents->"No_contents"|Dom_->"Dom"|Redirect_->"Redirect"|Reload_action{hidden;https}->letvalues=matchhidden,httpswith|false,false->"false, false"|false,true->"false, true"|true,false->"true, false"|true,true->"true, true"in"Reload_action with hidden and https as "^valuesletrechandle_result~replace~uriresult=let%lwtresult=resultinLwt_log.ign_debug~section:section_page("handle_result: result is "^string_of_resultresult);matchresultwith|Eliom_service.No_contents->Lwt.return_unit|Domd->change_url_string~replaceuri;set_content_locald|Redirectservice->change_page~replace~service()()|Reload_action{hidden;https}->(matchhidden,httpswith|false,false->reload_without_na_params~replace~uri~fallback:Eliom_service.reload_action|false,true->switch_to_https();reload_without_na_params~replace~uri~fallback:Eliom_service.reload_action_https|true,false->reload~replace~uri~fallback:Eliom_service.reload_action_hidden|true,true->switch_to_https();reload~replace~uri~fallback:Eliom_service.reload_action_https_hidden)(* == Main (exported) function: change the content of the page without
leaving the javascript application. See [change_page_uri] for the
function used to change page when clicking a link and
[change_page_{get,post}_form] when submiting a form. *)andchange_page:'get'post'meth'attached'co'ext'reg'tipo'gn'pn.?ignore_client_fun:bool->?replace:bool->?window_name:string->?window_features:string->?absolute:bool->?absolute_path:bool->?https:bool->service:('get,'post,'meth,'attached,'co,'ext,'reg,'tipo,'gn,'pn,Eliom_service.non_ocaml)Eliom_service.t->?hostname:string->?port:int->?fragment:string->?keep_nl_params:[`All|`None|`Persistent]->?nl_params:Eliom_parameter.nl_params_set->?keep_get_na_params:bool->?progress:(int->int->unit)->?upload_progress:(int->int->unit)->?override_mime_type:string->'get->'post->unitLwt.t=fun(typem)?(ignore_client_fun=false)?(replace=false)?window_name?window_features?absolute?absolute_path?https~(service:(_,_,m,_,_,_,_,_,_,_,_)Eliom_service.t)?hostname?port?fragment?keep_nl_params?(nl_params=Eliom_parameter.empty_nl_params_set)?keep_get_na_params?progress?upload_progress?override_mime_typeget_paramspost_params->Lwt_log.ign_debug~section:section_page"Change page";letxhr=Eliom_service.xhr_with_cookiesserviceinifxhr=None||(https=Sometrue&¬Eliom_request_info.ssl_)||(https=Somefalse&&Eliom_request_info.ssl_)||(window_name<>None&&window_name<>Some"_self")thenlet()=Lwt_log.ign_debug~section:section_page"change page: xhr is None"inLwt.return(exit_to?window_name?window_features?absolute?absolute_path?https~service?hostname?port?fragment?keep_nl_params~nl_params?keep_get_na_paramsget_paramspost_params)elsewith_progress_cursor(matchxhrwith|Some(Sometmplast)whent=Eliom_request_info.get_request_template()->Lwt_log.ign_debug~section:section_page"change page: xhr is Some of get request template";letnl_params=Eliom_parameter.add_nl_parameternl_paramsEliom_request.nl_templatetmplinlet%lwturi,content=raw_call_service?absolute?absolute_path?https~service?hostname?port?fragment?keep_nl_params~nl_params?keep_get_na_params?progress?upload_progress?override_mime_typeget_paramspost_paramsinset_template_content~replace~uri?fragment(Somecontent)|_->(matchEliom_service.client_funservicewith|Somefwhennotignore_client_fun->Lwt_log.ign_debug~section:section_page"change page: client_fun service is Some and (not ignore_client_fun)";(* The service has a client side implementation.
We do not make the request *)(* I record the function to be used for void coservices: *)Eliom_lib.Option.iter(funrf->reload_function:=Some(fun()->rfget_params))(Eliom_service.reload_funservice);leturi,l,l'=matchcreate_request_~absolute:true?absolute_path?https~service?hostname?port?fragment?keep_nl_params~nl_params?keep_get_na_paramsget_paramspost_paramswith|`Get(uri,l)->uri,l,None|`Post(uri,l,l')|`Put(uri,l,l')|`Delete(uri,l,l')->uri,l,Some(ocamlify_paramsl')inletl=ocamlify_paramslinEliom_request_info.update_session_info~path:(Url.path_of_url_stringuri)~all_get_params:l~all_post_params:l'@@fun()->let%lwt()=run_lwt_callbacks{in_cache=is_in_cache!active_page.page_id;origin_uri=get_current_uri();target_uri=uri;origin_id=!active_page.page_id.state_index;target_id=None}(flush_onchangepage())inwith_new_page~replace()@@fun()->handle_result~replace~uri(fget_paramspost_params)|Nonewhenis_client_app()->Lwt_log.ign_debug~section:section_page"change page: client_fun service is None and is_client_app";Lwt.return@@exit_to?absolute?absolute_path?https~service?hostname?port?fragment?keep_nl_params~nl_params?keep_get_na_paramsget_paramspost_params|_->Lwt_log.ign_debug~section:section_page"change page: client_fun service is anything else";ifis_client_app()thenfailwith(Printf.sprintf"change page: no client-side service (%b)"ignore_client_fun);(* No client-side implementation *)with_new_page~replace()@@fun()->reload_function:=None;letcookies_info=Eliom_uri.make_cookies_info(https,service)inlet%lwturi,content=matchcreate_request_?absolute?absolute_path?https~service?hostname?port?fragment?keep_nl_params~nl_params?keep_get_na_paramsget_paramspost_paramswith|`Get(uri,_)->Eliom_request.http_get~expecting_process_page:true?cookies_infouri[]Eliom_request.xml_result|`Post(uri,_,p)->Eliom_request.http_post~expecting_process_page:true?cookies_infouripEliom_request.xml_result|`Put(uri,_,p)->Eliom_request.http_put~expecting_process_page:true?cookies_infouripEliom_request.xml_result|`Delete(uri,_,p)->Eliom_request.http_delete~expecting_process_page:true?cookies_infouripEliom_request.xml_resultinleturi,fragment=Url.split_fragmenturiinset_content~replace~uri?fragmentcontent))andchange_page_unknown?meth?hostname:_?(replace=false)i_subpathi_get_paramsi_post_params=Lwt_log.ign_debug~section:section_page"Change page unknown";leti_sess_info=Eliom_request_info.get_sess_info()andi_meth=matchmeth,i_post_paramswith|Somemeth,_->(meth:[`Get|`Post|`Put|`Delete]:>Eliom_common.meth)|None,[]->`Get|_,_->`Postinwith_new_page~replace()@@fun()->let%lwturi,result=route{Eliom_route.i_sess_info;i_subpath;i_meth;i_get_params;i_post_params}inhandle_result~replace~uri(Lwt.returnresult)andreload~replace~uri~fallback=Lwt_log.ign_debug~section:section_page"reload";letpath,args=path_and_args_of_uriuriintry%lwtchange_page_unknown~replacepathargs[]with_->change_page~replace~ignore_client_fun:true~service:fallback()()andreload_without_na_params~replace~uri~fallback=letpath,args=path_and_args_of_uriuriinletargs=Eliom_common.remove_na_prefix_paramsargsinLwt_log.ign_debug~section:section_page"reload_without_na_params";try%lwtchange_page_unknown~replacepathargs[]with_->change_page~replace~ignore_client_fun:true~service:fallback()()(* Function used in "onclick" event handler of <a>. *)letchange_page_uri_a?cookies_info?tmpl?(get_params=[])full_uri=Lwt_log.ign_debug~section:section_page"Change page uri";with_progress_cursor(leturi,fragment=Url.split_fragmentfull_uriinifuri<>get_current_uri()||fragment=Nonethen(ifis_client_app()thenfailwith"Change_page_uri_a called on client app";matchtmplwith|Sometwhentmpl=Eliom_request_info.get_request_template()->let%lwturi,content=Eliom_request.http_get?cookies_infouri((Eliom_request.nl_template_string,t)::get_params)Eliom_request.string_resultinset_template_content~replace:false~uri?fragmentcontent|_->let%lwturi,content=Eliom_request.http_get~expecting_process_page:true?cookies_infouriget_paramsEliom_request.xml_resultinset_content~replace:false~uri?fragmentcontent)else(change_url_string~replace:truefull_uri;scroll_to_fragmentfragment;Lwt.return_unit))letchange_page_uri?replacefull_uri=Lwt_log.ign_debug~section:section_page"Change page uri";try%lwtmatchUrl.url_of_stringfull_uriwith|Some(Url.Httpurl|Url.Httpsurl)->Lwt_log.ign_debug~section:section_page"change page uri: url is http or https";change_page_unknown?replaceurl.Url.hu_pathurl.Url.hu_arguments[]|_->failwith"invalid url"with_->ifis_client_app()thenfailwith(Printf.sprintf"Change page uri: can't find service for %s"full_uri)else(Lwt_log.ign_debug~section"Change page uri: resort to server";change_page_uri_afull_uri)(* Functions used in "onsubmit" event handler of <form>. *)letchange_page_get_form?cookies_info?tmplformfull_uri=with_progress_cursor(letform=Js.Unsafe.coerceforminleturi,fragment=Url.split_fragmentfull_uriinmatchtmplwith|Sometwhentmpl=Eliom_request_info.get_request_template()->let%lwturi,content=Eliom_request.send_get_form~get_args:[Eliom_request.nl_template_string,t]?cookies_infoformuriEliom_request.string_resultinset_template_content~replace:false~uri?fragmentcontent|_->let%lwturi,content=Eliom_request.send_get_form~expecting_process_page:true?cookies_infoformuriEliom_request.xml_resultinset_content~replace:false~uri?fragmentcontent)letchange_page_post_form?cookies_info?tmplformfull_uri=with_progress_cursor(letform=Js.Unsafe.coerceforminleturi,fragment=Url.split_fragmentfull_uriinmatchtmplwith|Sometwhentmpl=Eliom_request_info.get_request_template()->let%lwturi,content=Eliom_request.send_post_form~get_args:[Eliom_request.nl_template_string,t]?cookies_infoformuriEliom_request.string_resultinset_template_content~replace:false~uri?fragmentcontent|_->let%lwturi,content=Eliom_request.send_post_form~expecting_process_page:true?cookies_infoformuriEliom_request.xml_resultinset_content~replace:false~uri?fragmentcontent)let_=(Eliom_client_core.change_page_uri_:=fun?cookies_info?tmplhref->Lwt.ignore_result(change_page_uri_a?cookies_info?tmplhref));(Eliom_client_core.change_page_get_form_:=fun?cookies_info?tmplformhref->Lwt.ignore_result(change_page_get_form?cookies_info?tmplformhref));Eliom_client_core.change_page_post_form_:=fun?cookies_info?tmplformhref->Lwt.ignore_result(change_page_post_form?cookies_info?tmplformhref)(* == Main (internal) function: change the content of the page without leaving
the javascript application. *)(* == Navigating through the history... *)(* Given a state_id, [replace_page_in_history] replaces the current DOM with a
DOM from the DOM cache. *)letrestore_history_domid=matchHistory.find_by_state_indexidwith|Somepage->(matchpage.domwith|Somedom->if!only_replace_bodythenDom.replaceChildDom_html.document##.documentElementdomDom_html.document##.bodyelseDom.replaceChildDom_html.documentdomDom_html.document##.documentElement|None->Lwt_log.ign_error~section"DOM not actually cached");set_active_pagepage|_->Lwt_log.ign_error~section"cannot find DOM in history"letwait_load_end=Eliom_client_core.wait_load_endlet()=ifEliom_process.history_apithen(letrevisitfull_uristate_id=letstate=tryget_statestate_idwithNot_found->failwith(Printf.sprintf"revisit: state id %x/%x not found in sessionStorage (%s)"state_id.session_idstate_id.state_indexfull_uri)inlettarget_id=state_id.state_indexinletev={in_cache=is_in_cachestate_id;origin_uri=get_current_uri();target_uri=full_uri;origin_id=!active_page.page_id.state_index;target_id=Sometarget_id}inlettmpl=state.templateinLwt.ignore_result@@with_progress_cursor@@leturi,fragment=Url.split_fragmentfull_uriinifuri=get_current_uri()then(Lwt_log.ign_debug~section:section_page"revisit: uri = get_current_uri";!active_page.page_id<-state_id;scroll_to_fragment~offset:state.positionfragment;Lwt.return_unit)elsetry(* serve cached page from the from history_doms *)Lwt_log.ign_debug~section:section_page"revisit: uri != get_current_uri";ifnot(is_in_cachestate_id)thenraiseNot_found;let%lwt()=run_lwt_callbacksev(flush_onchangepage())inrestore_history_domtarget_id;set_current_uriuri;let%lwt()=Js_of_ocaml_lwt.Lwt_js_events.request_animation_frame()inscroll_to_fragment~offset:state.positionfragment;(* Wait for the dom to be repainted before scrolling *)let%lwt()=Js_of_ocaml_lwt.Lwt_js_events.request_animation_frame()inscroll_to_fragment~offset:state.positionfragment;(* When we use iPhone, we need to wait for one more
[request_animation_frame] before scrolling.The
function [scroll_to_fragment] is called twice. In
other words, we want to call [scroll_to_fragment]
as early as possible so that the scroll position
will not jump after the second [request_animation_frame]
if the dom has already be painted after the first one. *)Lwt.return_unitwithNot_found->(letsession_changed=state_id.session_id<>session_idinifsession_changed&&is_client_app()thenfailwith(Printf.sprintf"revisit: session changed on client: %d => %d (%s)"state_id.session_idsession_idfull_uri);try(* same session *)ifsession_changedthenraiseNot_found;Lwt_log.ign_debug~section:section_page"revisit: session has not changed";letold_page=History.find_by_state_indexstate_id.state_indexinletrf=Option.bindold_page@@fun{reload_function=rf;_}->rfinmatchrfwith|None->raiseNot_found|Somef->reload_function:=rf;let%lwt()=run_lwt_callbacksev(flush_onchangepage())inwith_new_page~state_id?old_page~replace:false()@@fun()->set_current_uriuri;History.replace(get_this_page());let%lwt()=match%lwtf()()with|Eliom_service.Domd->set_content_locald|r->handle_result~uri:(get_current_uri())~replace:true(Lwt.returnr)inscroll_to_fragment~offset:state.positionfragment;Lwt.return_unitwithNot_found->((* different session ID *)set_current_uriuri;matchtmplwith|Sometwhentmpl=Eliom_request_info.get_request_template()->Lwt_log.ign_debug~section:section_page"revisit: template is Some and equals to get_request_template";let%lwturi,content=Eliom_request.http_geturi[Eliom_request.nl_template_string,t]Eliom_request.string_resultinlet%lwt()=set_template_contentcontent~replace:true~uriinscroll_to_fragment~offset:state.positionfragment;Lwt.return_unit|_->ifis_client_app()thenfailwith(Printf.sprintf"revisit: could not generate page client-side (%s)"full_uri);Lwt_log.ign_debug~section:section_page"revisit: template is anything else";with_new_page?state_id:(ifsession_changedthenNoneelseSomestate_id)~replace:false()@@fun()->let%lwturi,content=Eliom_request.http_get~expecting_process_page:trueuri[]Eliom_request.xml_resultinlet%lwt()=set_content~uri~replace:true~offset:state.position?fragmentcontentinLwt.return_unit))inletrevisit_wrapperfull_uristate_id=Lwt_log.ign_debug~section:section_page"revisit_wrapper";(* CHECKME: is it OK that set_state happens after the unload
callbacks are executed? *)letf()=update_state();revisitfull_uristate_idandcancel()=()inrun_onunload_wrapperfcancelinLwt.ignore_result(let%lwt()=wait_load_end()inLwt_log.ign_debug~section:section_page"revisit_wrapper: replaceState";Dom_html.window##.history##(replaceState(Js.Opt.return(!active_page.page_id,Dom_html.window##.location##.href))(Js.string"")Js.null);Lwt.return_unit);Dom_html.window##.onpopstate:=Dom_html.handler(funevent->Lwt_log.ign_debug~section:section_page"revisit_wrapper: onpopstate";Eliommod_dom.touch_base();Js.Opt.case((Js.Unsafe.coerceevent)##.state:(state_id*Js.js_stringJs.t)Js.opt)(fun()->()(* Ignore dummy popstate event fired by chromium. *))(fun(state,full_uri)->revisit_wrapper(Js.to_stringfull_uri)state);Js._false))else(* Without history API *)(* FIXME: This should be adapted to work with template...
Solution: add the "state_id" in the fragment ??
*)letread_fragment()=Js.to_stringDom_html.window##.location##.hashinletauto_change_pagefragment=Lwt.ignore_result(letl=String.lengthfragmentinifl=0||(l>1&&fragment.[1]='!')theniffragment<>!current_pseudo_fragmentthen(current_pseudo_fragment:=fragment;leturi=matchlwith|2->"./"(* fix for firefox *)|0|1->fst(Url.split_fragmentUrl.Current.as_string)|_->String.subfragment2(String.lengthfragment-2)inLwt_log.ign_debug~section:section_page"auto_change_page";(* CCC TODO handle templates *)change_page_uriuri)elseLwt.return_unitelseLwt.return_unit)inEliommod_dom.onhashchange(funs->auto_change_page(Js.to_strings));letfirst_fragment=read_fragment()iniffirst_fragment<>!current_pseudo_fragmentthenLwt.ignore_result(let%lwt()=wait_load_end()inauto_change_pagefirst_fragment;Lwt.return_unit)let()=Eliom_unwrap.register_unwrapper(Eliom_unwrap.id_of_intEliom_common_base.server_function_unwrap_id_int)(fun(service,_)->(* 2013-07-31 I make all RPC's absolute because otherwise
it does not work with mobile apps.
Is it a problem?
-- Vincent *)call_ocaml_service~absolute:true~service())letget_application_name=Eliom_process.get_application_nameletset_client_html_file=Eliom_common.set_client_html_fileletmiddleClick=Eliom_client_core.middleClicktypeclient_form_handler=Eliom_client_core.client_form_handlermoduleAdditional_headers=Eliom_request.Additional_headers