source: trunk/TaskDataPage.cpp @ 3

Revision 3, 16.0 KB checked in by lowjoel, 7 years ago (diff)

Added the source files from root (SVN migration commit 1)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1// TaskDataPage.cpp
2//
3// Eraser. Secure data removal. For Windows.
4// Copyright © 1997-2001  Sami Tolvanen (sami@tolvanen.com).
5//
6// This program is free software; you can redistribute it and/or
7// modify it under the terms of the GNU General Public License
8// as published by the Free Software Foundation; either version 2
9// of the License, or (at your option) any later version.
10//
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14// GNU General Public License for more details.
15//
16// You should have received a copy of the GNU General Public License
17// along with this program; if not, write to the Free Software
18// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
19// 02111-1307, USA.
20
21#include "stdafx.h"
22#include "resource.h"
23#include "shared\DirDialog.h"
24#include "shared\NewDialog.h"
25#include "TaskDataPage.h"
26#include ".\taskdatapage.h"
27
28#ifdef _DEBUG
29#undef THIS_FILE
30static char BASED_CODE THIS_FILE[] = __FILE__;
31#endif
32
33IMPLEMENT_DYNCREATE(CTaskDataPage, CPropertyPage)
34IMPLEMENT_DYNCREATE(CTaskSchedulePage, CPropertyPage)
35IMPLEMENT_DYNCREATE(CTaskStatisticsPage, CPropertyPage)
36
37
38/////////////////////////////////////////////////////////////////////////////
39// CTaskDataPage property page
40
41CTaskDataPage::CTaskDataPage() :
42CPropertyPage(CTaskDataPage::IDD),
43m_tType(Drive),
44m_bShowPersistent(FALSE)
45, m_dwFinishAction(0)
46,m_iFinishActionInd(0)
47{
48    //{{AFX_DATA_INIT(CTaskDataPage)
49    m_strFolder = _T("");
50    m_strFile = _T("");
51    m_strMask = _T("");
52    m_bRemoveOnlySub = FALSE;
53    m_bSubfolders = FALSE;
54    m_bRemoveFolder = FALSE;
55    m_bPersistent = FALSE;
56    m_bUseWildCards = FALSE;
57    m_bWildCardsInSubfolders = FALSE;
58    //}}AFX_DATA_INIT
59
60//    m_psp.dwFlags &= (~PSP_HASHELP);
61}
62
63CTaskDataPage::~CTaskDataPage()
64{
65}
66
67void CTaskDataPage::DoDataExchange(CDataExchange* pDX)
68{
69    CPropertyPage::DoDataExchange(pDX);
70    //{{AFX_DATA_MAP(CTaskDataPage)
71    DDX_Control(pDX, IDC_COMBO_DRIVES, m_comboDrives);
72    DDX_Text(pDX, IDC_EDIT_FOLDER, m_strFolder);
73    DDX_Text(pDX, IDC_EDIT_FILE, m_strFile);
74    DDX_Text(pDX, IDC_EDIT_MASK, m_strMask);
75    DDX_Check(pDX, IDC_CHECK_ONLYSUB, m_bRemoveOnlySub);
76    DDX_Check(pDX, IDC_CHECK_SUBFOLDERS, m_bSubfolders);
77    DDX_Check(pDX, IDC_CHECK_FOLDER, m_bRemoveFolder);
78    DDX_Control(pDX, IDC_RADIO_DISK, m_buRadioDisk);
79    DDX_Control(pDX, IDC_RADIO_FILES, m_buRadioFiles);
80    DDX_Control(pDX, IDC_RADIO_FILE, m_buRadioFile);
81    DDX_Control(pDX, IDC_RADIO_MASK, m_buRadioMask);
82    DDX_Check(pDX, IDC_PERSISTENT_CHECK, m_bPersistent);
83    DDX_Check(pDX, IDC_CHECK_WILDCARDS, m_bUseWildCards);
84    DDX_Check(pDX, IDC_CHECK_WILDCARDS_SF, m_bWildCardsInSubfolders);
85    //}}AFX_DATA_MAP
86   
87    DDX_CBIndex(pDX, IDC_COMBO_WHENFINISH, m_iFinishActionInd);
88    if (-1 !=m_iFinishActionInd && pDX->m_bSaveAndValidate)
89    {
90
91        CComboBox* finish_action  = (CComboBox* )GetDlgItem(IDC_COMBO_WHENFINISH);
92        m_dwFinishAction = finish_action->GetItemData(m_iFinishActionInd) ;
93       
94    }
95}
96
97
98BEGIN_MESSAGE_MAP(CTaskDataPage, CPropertyPage)
99    //{{AFX_MSG_MAP(CTaskDataPage)
100    ON_BN_CLICKED(IDC_BUTTON_BROWSE, OnBrowse)
101    ON_BN_CLICKED(IDC_BUTTON_BROWSE_FILES, OnBrowseFiles)
102    ON_BN_CLICKED(IDC_CHECK_FOLDER, OnRemoveFolder)
103    ON_BN_CLICKED(IDC_RADIO_DISK, OnRadioDisk)
104    ON_BN_CLICKED(IDC_RADIO_FILES, OnRadioFiles)
105    ON_BN_CLICKED(IDC_RADIO_FILE, OnRadioFile)
106    ON_BN_CLICKED(IDC_RADIO_MASK, OnRadioMask)
107    ON_BN_CLICKED(IDC_CHECK_WILDCARDS, OnCheckWildcards)
108    //}}AFX_MSG_MAP
109END_MESSAGE_MAP()
110
111
112/////////////////////////////////////////////////////////////////////////////
113// CTaskSchedulePage property page
114
115CTaskSchedulePage::CTaskSchedulePage() :
116CPropertyPage(CTaskSchedulePage::IDD),
117m_b24Hour(TRUE)
118{
119    //{{AFX_DATA_INIT(CTaskSchedulePage)
120    m_bPM = FALSE;
121    m_iWhen = Day;
122    //}}AFX_DATA_INIT
123}
124
125CTaskSchedulePage::~CTaskSchedulePage()
126{
127}
128
129void CTaskSchedulePage::DoDataExchange(CDataExchange* pDX)
130{
131    CPropertyPage::DoDataExchange(pDX);
132    //{{AFX_DATA_MAP(CTaskSchedulePage)
133    DDX_Control(pDX, IDC_EDIT_TIME, m_editTime);
134    DDX_Check(pDX, IDC_CHECK_PM, m_bPM);
135    DDX_CBIndex(pDX, IDC_COMBO_WHEN, m_iWhen);
136    //}}AFX_DATA_MAP
137}
138
139
140BEGIN_MESSAGE_MAP(CTaskSchedulePage, CPropertyPage)
141    //{{AFX_MSG_MAP(CTaskSchedulePage)
142    //}}AFX_MSG_MAP
143END_MESSAGE_MAP()
144
145
146/////////////////////////////////////////////////////////////////////////////
147// CTaskSchedulePage property page
148
149CTaskStatisticsPage::CTaskStatisticsPage() :
150CPropertyPage(CTaskStatisticsPage::IDD),
151m_lpts(0)
152{
153    //{{AFX_DATA_INIT(CTaskStatisticsPage)
154    m_strStatistics = _T("");
155    //}}AFX_DATA_INIT
156}
157
158CTaskStatisticsPage::~CTaskStatisticsPage()
159{
160}
161
162void CTaskStatisticsPage::DoDataExchange(CDataExchange* pDX)
163{
164    CPropertyPage::DoDataExchange(pDX);
165    //{{AFX_DATA_MAP(CTaskStatisticsPage)
166    DDX_Text(pDX, IDC_EDIT_STATISTICS, m_strStatistics);
167    //}}AFX_DATA_MAP
168}
169
170
171BEGIN_MESSAGE_MAP(CTaskStatisticsPage, CPropertyPage)
172    //{{AFX_MSG_MAP(CTaskStatisticsPage)
173    ON_BN_CLICKED(IDC_BUTTON_RESET, OnButtonReset)
174    //}}AFX_MSG_MAP
175END_MESSAGE_MAP()
176
177
178
179void CTaskDataPage::OnBrowse()
180{
181    UpdateData(TRUE);
182
183    //CDirectoryDialog dd;
184    //dd.DoModal(m_strFolder, "Select Folder to be Erased.");
185
186    CNewDialog dd;
187    if (dd.DoModal() == IDOK) {
188        m_strFolder = dd.m_sPath;
189    }
190   
191    UpdateData(FALSE);
192}
193
194void CTaskDataPage::OnBrowseFiles()
195{
196    UpdateData(TRUE);
197
198    // Was CfileDialogEx now with MFC7 we can change back to MFC Class
199    /*CFileDialog fdlg(TRUE, NULL, NULL,
200                       OFN_PATHMUSTEXIST |OFN_ENABLESIZING | OFN_NODEREFERENCELINKS | OFN_FILEMUSTEXIST | OFN_SHOWHELP | OFN_OVERWRITEPROMPT,
201                       "All Files (*.*) | *.*||", this);*/
202    //CCustomFileDialog fdlg(TRUE, OFN_EXPLORER|OFN_PATHMUSTEXIST |OFN_ENABLESIZING |OFN_NODEREFERENCELINKS | OFN_FILEMUSTEXIST | OFN_SHOWHELP);
203   
204    //fdlg.m_ofn.lpstrTitle = "Select File to be Erased";
205    CNewDialog fdlg;
206    if (fdlg.DoModal() == IDOK)
207    {
208        m_strFile = fdlg.m_sPath;//fdlg.GetPathName();
209        UpdateData(FALSE);
210    }
211}
212
213void CTaskDataPage::OnRemoveFolder()
214{
215    UpdateData(TRUE);
216
217    // if the user wants to remove the specified folder, we
218    // must remove the subfolders as well
219
220    if (m_bRemoveFolder)
221    {
222        m_bSubfolders = TRUE;
223        GetDlgItem(IDC_CHECK_SUBFOLDERS)->EnableWindow(FALSE);
224        GetDlgItem(IDC_CHECK_ONLYSUB)->EnableWindow(TRUE);
225    }
226    else
227    {
228        GetDlgItem(IDC_CHECK_SUBFOLDERS)->EnableWindow(TRUE);
229        GetDlgItem(IDC_CHECK_ONLYSUB)->EnableWindow(FALSE);
230    }
231
232    UpdateData(FALSE);
233}
234
235void CTaskDataPage::OnRadioDisk()
236{
237    // enable disk section
238    GetDlgItem(IDC_COMBO_DRIVES)->EnableWindow(TRUE);
239
240    // disable other sections
241    GetDlgItem(IDC_BUTTON_BROWSE)->EnableWindow(FALSE);
242    GetDlgItem(IDC_EDIT_FOLDER)->EnableWindow(FALSE);
243    GetDlgItem(IDC_CHECK_FOLDER)->EnableWindow(FALSE);
244    GetDlgItem(IDC_CHECK_SUBFOLDERS)->EnableWindow(FALSE);
245    GetDlgItem(IDC_CHECK_ONLYSUB)->EnableWindow(FALSE);
246
247    GetDlgItem(IDC_BUTTON_BROWSE_FILES)->EnableWindow(FALSE);
248    GetDlgItem(IDC_EDIT_FILE)->EnableWindow(FALSE);
249    GetDlgItem(IDC_CHECK_WILDCARDS)->EnableWindow(FALSE);
250    GetDlgItem(IDC_CHECK_WILDCARDS_SF)->EnableWindow(FALSE);
251    ((CEdit*)GetDlgItem(IDC_EDIT_MASK))->SetReadOnly(TRUE);
252}
253
254void CTaskDataPage::OnRadioFiles()
255{
256    // enable folder section
257    GetDlgItem(IDC_BUTTON_BROWSE)->EnableWindow(TRUE);
258    GetDlgItem(IDC_EDIT_FOLDER)->EnableWindow(TRUE);
259    GetDlgItem(IDC_CHECK_FOLDER)->EnableWindow(TRUE);
260    GetDlgItem(IDC_CHECK_SUBFOLDERS)->EnableWindow(!m_bRemoveFolder);
261    GetDlgItem(IDC_CHECK_ONLYSUB)->EnableWindow(m_bRemoveFolder);
262
263    // disable other sections
264    GetDlgItem(IDC_COMBO_DRIVES)->EnableWindow(FALSE);
265
266    GetDlgItem(IDC_BUTTON_BROWSE_FILES)->EnableWindow(FALSE);
267    GetDlgItem(IDC_EDIT_FILE)->EnableWindow(FALSE);
268    GetDlgItem(IDC_CHECK_WILDCARDS)->EnableWindow(FALSE);
269    GetDlgItem(IDC_CHECK_WILDCARDS_SF)->EnableWindow(FALSE);
270    ((CEdit*)GetDlgItem(IDC_EDIT_MASK))->SetReadOnly(TRUE);
271}
272
273void CTaskDataPage::OnRadioFile()
274{
275    // enable file section
276    GetDlgItem(IDC_BUTTON_BROWSE_FILES)->EnableWindow(TRUE);
277    GetDlgItem(IDC_EDIT_FILE)->EnableWindow(TRUE);
278    ((CEdit*)GetDlgItem(IDC_EDIT_FILE))->SetReadOnly(!m_bUseWildCards);
279    GetDlgItem(IDC_CHECK_WILDCARDS)->EnableWindow(TRUE);
280    GetDlgItem(IDC_CHECK_WILDCARDS_SF)->EnableWindow(m_bUseWildCards);
281
282    // disable other sections
283    GetDlgItem(IDC_COMBO_DRIVES)->EnableWindow(FALSE);
284    ((CEdit*)GetDlgItem(IDC_EDIT_MASK))->SetReadOnly(TRUE);
285    GetDlgItem(IDC_BUTTON_BROWSE)->EnableWindow(FALSE);
286    GetDlgItem(IDC_EDIT_FOLDER)->EnableWindow(FALSE);
287    GetDlgItem(IDC_CHECK_FOLDER)->EnableWindow(FALSE);
288    GetDlgItem(IDC_CHECK_SUBFOLDERS)->EnableWindow(FALSE);
289    GetDlgItem(IDC_CHECK_ONLYSUB)->EnableWindow(FALSE);
290
291}
292
293#include "shared\FileHelper.h"
294void CTaskDataPage::OnRadioMask()
295{
296   
297    // enable file section
298    GetDlgItem(IDC_EDIT_MASK)->EnableWindow(TRUE);
299    ((CEdit*)GetDlgItem(IDC_EDIT_MASK))->SetReadOnly(FALSE);
300    // disable other sections
301    GetDlgItem(IDC_COMBO_DRIVES)->EnableWindow(FALSE);
302
303    GetDlgItem(IDC_BUTTON_BROWSE)->EnableWindow(FALSE);
304    GetDlgItem(IDC_EDIT_FOLDER)->EnableWindow(FALSE);
305    GetDlgItem(IDC_EDIT_FILE)->EnableWindow(FALSE);
306    GetDlgItem(IDC_CHECK_WILDCARDS)->EnableWindow(FALSE);
307    GetDlgItem(IDC_CHECK_WILDCARDS_SF)->EnableWindow(FALSE);
308    GetDlgItem(IDC_CHECK_FOLDER)->EnableWindow(FALSE);
309    GetDlgItem(IDC_CHECK_SUBFOLDERS)->EnableWindow(FALSE);
310    GetDlgItem(IDC_CHECK_ONLYSUB)->EnableWindow(FALSE);
311
312}
313
314void CTaskDataPage::OnCheckWildcards()
315{
316    UpdateData(TRUE);
317    ((CEdit*)GetDlgItem(IDC_EDIT_FILE))->SetReadOnly(!m_bUseWildCards);
318    GetDlgItem(IDC_CHECK_WILDCARDS_SF)->EnableWindow(m_bUseWildCards);
319}
320
321
322BOOL CTaskDataPage::OnInitDialog()
323{
324    CPropertyPage::OnInitDialog();
325
326    BOOL bDrive     = (m_tType == Drive);
327    BOOL bFolder    = (m_tType == Folder);
328    BOOL bFile      = (m_tType == File);
329    BOOL bMask      = (m_tType == Mask);
330
331    m_comboDrives.FillDrives();
332
333    if (!m_strSelectedDrive.IsEmpty())
334        m_comboDrives.SelectDrive(m_strSelectedDrive);
335
336    m_buRadioDisk.SetCheck(bDrive);
337    m_buRadioFiles.SetCheck(bFolder);
338    m_buRadioFile.SetCheck(bFile);
339    m_buRadioMask.SetCheck(bMask);
340
341    // drive
342    GetDlgItem(IDC_COMBO_DRIVES)->EnableWindow(bDrive);
343
344    // folder
345    GetDlgItem(IDC_BUTTON_BROWSE)->EnableWindow(bFolder);
346    GetDlgItem(IDC_EDIT_FOLDER)->EnableWindow(bFolder);
347    GetDlgItem(IDC_CHECK_FOLDER)->EnableWindow(bFolder);
348    GetDlgItem(IDC_CHECK_SUBFOLDERS)->EnableWindow(bFolder && !m_bRemoveFolder);
349    GetDlgItem(IDC_CHECK_ONLYSUB)->EnableWindow(bFolder && m_bRemoveFolder);
350
351    // file
352    GetDlgItem(IDC_BUTTON_BROWSE_FILES)->EnableWindow(bFile);
353    GetDlgItem(IDC_EDIT_FILE)->EnableWindow(bFile);
354    ((CEdit*)GetDlgItem(IDC_EDIT_FILE))->SetReadOnly(!m_bUseWildCards);
355    GetDlgItem(IDC_CHECK_WILDCARDS)->EnableWindow(bFile);
356    GetDlgItem(IDC_CHECK_WILDCARDS_SF)->EnableWindow(bFile && m_bUseWildCards);
357
358    //mask
359    GetDlgItem(IDC_EDIT_MASK)->EnableWindow(bMask);
360    ((CEdit*)GetDlgItem(IDC_EDIT_MASK))->SetReadOnly(!bMask);
361   
362    if (!m_bShowPersistent)
363        GetDlgItem(IDC_PERSISTENT_CHECK)->ShowWindow(SW_HIDE);
364
365    CComboBox* finish_action  = (CComboBox*)GetDlgItem(IDC_COMBO_WHENFINISH);
366    int selstr;
367    finish_action->SetItemDataPtr(selstr = finish_action->AddString("None"), NULL);
368    finish_action->SetItemData(finish_action->AddString("Shutdown system"), EWX_SHUTDOWN);
369    finish_action->SetItemData(finish_action->AddString("Restart"), EWX_REBOOT);
370    finish_action->SetItemData(finish_action->AddString("Sleep"), -1);
371
372    finish_action->SetCurSel(m_iFinishActionInd);
373   
374
375    UpdateData(FALSE);
376    return TRUE;  // return TRUE unless you set the focus to a control
377                  // EXCEPTION: OCX Property Pages should return FALSE
378}
379
380void CTaskDataPage::OnOK()
381{
382    UpdateData(TRUE);
383
384    if (m_buRadioDisk.GetCheck())
385        m_tType = Drive;
386    else if (m_buRadioFiles.GetCheck())
387        m_tType = Folder;
388    else if (m_buRadioMask.GetCheck()) m_tType = Mask;
389    else m_tType = File;
390   
391
392
393    if (m_tType == Drive)
394        m_comboDrives.GetSelectedDrive(m_strSelectedDrive);
395    else if (m_tType == Folder && !m_strFolder.IsEmpty())
396    {
397        if (m_strFolder[m_strFolder.GetLength() - 1] != '\\')
398            m_strFolder += "\\";
399    }
400
401    CPropertyPage::OnOK();
402}
403
404void CTaskSchedulePage::OnOK()
405{
406    CPropertyPage::OnOK();
407
408    UpdateData(TRUE);
409
410    m_odtTime = m_editTime.GetTime();
411
412    if (!m_b24Hour)
413    {
414        // 12-hour clock
415
416        int iHour = m_odtTime.GetHour();
417        COleDateTimeSpan odtSpan(0, 12, 0, 0);
418
419        if ((iHour < 12 && m_bPM) ||
420            (iHour == 12 && !m_bPM))
421        {
422            m_odtTime += odtSpan;
423        }
424    }
425}
426
427BOOL CTaskSchedulePage::OnInitDialog()
428{
429    CPropertyPage::OnInitDialog();
430
431    TCHAR szLocale[2] = { 0, 0 };
432
433    if (GetLocaleInfo(LOCALE_USER_DEFAULT,
434                      LOCALE_ITIME,
435                      szLocale, 2))
436    {
437        // 0 -> 12-hour clock
438        // 1 -> 24-hour clock
439
440        m_b24Hour = (szLocale[0] != '0');
441    }
442
443    if (!m_b24Hour)
444    {
445        // 12-hour clock requires some special arrangements
446        int iHour;
447        CString str;
448
449        m_editTime.SetHours(12);
450        m_editTime.SetMinHours(1);
451
452        GetDlgItem(IDC_CHECK_PM)->ShowWindow(SW_SHOW);
453
454        iHour = m_odtTime.GetHour();
455
456        // 12 - 11 PM == 12 - 23
457        // 12 - 11 AM == 00 - 11
458
459        if (iHour < 1)
460        {
461            // 00 == 12 AM
462            iHour = 12;
463            m_bPM = FALSE;
464        }
465        else if (iHour > 12)
466        {
467            // 13 - 23 == 01 - 11 PM
468            iHour -= 12;
469            m_bPM = TRUE;
470        }
471        else if (iHour == 12)
472        {
473            // 12 == 12 PM
474            m_bPM = TRUE;
475        }
476        else
477        {
478            // 01 - 11 == 01 - 11 AM
479            m_bPM = FALSE;
480        }
481
482        str.Format("%.2d:%.2d", iHour, m_odtTime.GetMinute());
483        m_editTime.SetTime(str);
484    }
485    else
486    {
487        // 24-hour clock
488        m_editTime.SetTime(m_odtTime);
489    }
490
491    UpdateData(FALSE);
492
493    return TRUE;  // return TRUE unless you set the focus to a control
494                  // EXCEPTION: OCX Property Pages should return FALSE
495}
496
497BOOL CTaskStatisticsPage::OnInitDialog()
498{
499    UpdateStatistics();
500    CPropertyPage::OnInitDialog();
501
502    return TRUE;  // return TRUE unless you set the focus to a control
503                  // EXCEPTION: OCX Property Pages should return FALSE
504}
505
506void CTaskStatisticsPage::OnButtonReset()
507{
508    if (m_lpts)
509    {
510        m_lpts->Reset();
511        UpdateStatistics();
512        UpdateData(FALSE);
513    }
514}
515
516void CTaskStatisticsPage::UpdateStatistics()
517{
518    if (m_lpts)
519    {
520        CString strTemp;
521        double dTime;
522
523        // report header
524        m_strStatistics = "Task Report:\r\n\r\n";
525        // run
526        strTemp.Format("    Processed\t\t=  %u times\r\n", m_lpts->m_dwTimes);
527        m_strStatistics += strTemp;
528        // successful
529        strTemp.Format("    Successful\t\t=  %u times\r\n", m_lpts->m_dwTimesSuccess);
530        m_strStatistics += strTemp;
531        // terminated
532        strTemp.Format("    Terminated\t\t=  %u times\r\n", m_lpts->m_dwTimesInterrupted);
533        m_strStatistics += strTemp;
534        // failure
535        strTemp.Format("    Possible failure\t\t=  %u times\r\n",
536                        m_lpts->m_dwTimes - m_lpts->m_dwTimesSuccess - m_lpts->m_dwTimesInterrupted);
537        m_strStatistics += strTemp;
538
539        // statistics header
540        m_strStatistics += "\r\nStatistics (average):\r\n\r\n";
541        // erased area
542        strTemp.Format("    Erased area\t\t=  %u %s\r\n", m_lpts->m_dwAveArea, "kB");
543        m_strStatistics += strTemp;
544        // written
545        strTemp.Format("    Data written\t\t=  %u %s\r\n", m_lpts->m_dwAveWritten, "kB");
546        m_strStatistics += strTemp;
547        // time
548        dTime = (double)m_lpts->m_dwAveTime / 1000.0f;
549        strTemp.Format("    Write time\t\t=  %.2f %s", dTime, "s");
550        m_strStatistics += strTemp;
551        // speed
552        if (dTime > 0)
553        {
554            strTemp.Format("\r\n    Write speed\t\t=  %u %s", (DWORD)((double)m_lpts->m_dwAveWritten /
555                dTime), "kB/s");
556            m_strStatistics += strTemp;
557        }
558
559        UpdateData(FALSE);
560    }
561}
562
Note: See TracBrowser for help on using the repository browser.