66import user_agents
77
88from ..utils import catch_exp_call
9+ from ..exceptions import PageClosedException
910
1011logger = logging .getLogger (__name__ )
1112
@@ -62,7 +63,8 @@ def __init__(self, session_info):
6263 self .internal_save = dict (info = session_info ) # some session related info, just for internal used
6364 self .save = {} # underlying implement of `pywebio.session.data`
6465 self .scope_stack = defaultdict (lambda : ['ROOT' ]) # task_id -> scope栈
65- self .page_stack = defaultdict (lambda : []) # task_id -> page id
66+ self .page_stack = defaultdict (lambda : []) # task_id -> page id stack
67+ self .active_page = defaultdict (set ) # task_id -> activate page set
6668
6769 self .deferred_functions = [] # 会话结束时运行的函数
6870 self ._closed = False
@@ -96,24 +98,49 @@ def push_scope(self, name):
9698 self .scope_stack [task_id ].append (name )
9799
98100 def get_page_id (self ):
101+ """
102+ get the if of current page in task, return `None` when it's master page,
103+ raise PageClosedException when current page is closed
104+ """
99105 task_id = type (self ).get_current_task_id ()
100- if task_id not in self .page_stack :
101- return None
102- try :
103- return self .page_stack [task_id ][- 1 ]
104- except IndexError :
106+ if task_id not in self .page_stack or not self .page_stack [task_id ]:
107+ # current in master page
105108 return None
106109
110+ page_id = self .page_stack [task_id ][- 1 ]
111+ if page_id not in self .active_page [task_id ]:
112+ raise PageClosedException (
113+ "The page is closed by app user, "
114+ "you see this exception mostly because you set `silent_quit=False` in `pywebio.output.page()`"
115+ )
116+
117+ return page_id
118+
107119 def pop_page (self ):
120+ """exit the current page in task"""
108121 task_id = type (self ).get_current_task_id ()
109122 try :
110- return self .page_stack [task_id ].pop ()
123+ page_id = self .page_stack [task_id ].pop ()
111124 except IndexError :
112125 raise ValueError ("Internal Error: No page to exit" ) from None
113126
127+ try :
128+ self .active_page [task_id ].remove (page_id )
129+ except KeyError :
130+ pass
131+ return page_id
132+
114133 def push_page (self , page_id ):
115134 task_id = type (self ).get_current_task_id ()
116135 self .page_stack [task_id ].append (page_id )
136+ self .active_page [task_id ].add (page_id )
137+
138+ def notify_page_lost (self , task_id , page_id ):
139+ """update page status when there is page lost"""
140+ try :
141+ self .active_page [task_id ].remove (page_id )
142+ except KeyError :
143+ pass
117144
118145 def send_task_command (self , command ):
119146 raise NotImplementedError
@@ -123,7 +150,13 @@ def next_client_event(self) -> dict:
123150 raise NotImplementedError
124151
125152 def send_client_event (self , event ):
126- raise NotImplementedError
153+ """send event from client to session,
154+ return True when this event is handled by this method, which means the subclass must ignore this event.
155+ """
156+ if event ['event' ] == 'page_close' :
157+ self .notify_page_lost (event ['task_id' ], event ['data' ])
158+ return True
159+ return False
127160
128161 def get_task_commands (self ) -> list :
129162 raise NotImplementedError
@@ -185,6 +218,10 @@ def defer_call(self, func):
185218 self .deferred_functions .append (func )
186219
187220 def need_keep_alive (self ) -> bool :
221+ """
222+ return whether to need to hold this session if it runs over now.
223+ if the session maintains some event callbacks, it needs to hold session unit user close the session
224+ """
188225 raise NotImplementedError
189226
190227
0 commit comments