Bugzilla – Attachment 181874 Details for
Bug 36586
Self-checkouts will get CSRF errors if left inactive for 8 hours
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 36586: Define self-checkin timer using Javascript class
Bug-36586-Define-self-checkin-timer-using-Javascri.patch (text/plain), 28.47 KB, created by
Martin Renvoize (ashimema)
on 2025-05-02 15:31:33 UTC
(
hide
)
Description:
Bug 36586: Define self-checkin timer using Javascript class
Filename:
MIME Type:
Creator:
Martin Renvoize (ashimema)
Created:
2025-05-02 15:31:33 UTC
Size:
28.47 KB
patch
obsolete
>From e04448b767ddac18b183b17ea5bffb0567715091 Mon Sep 17 00:00:00 2001 >From: David Cook <dcook@prosentient.com.au> >Date: Wed, 4 Dec 2024 21:10:55 +0000 >Subject: [PATCH] Bug 36586: Define self-checkin timer using Javascript class > >This patch rewrites the self-checkin timer code to be defined in a >Javascript class which is then instantiated in once in sci-main.pl >depending on certain conditions (ie a barcode has been entered, or >an item has been checked in by barcode). > >To test: >1 - Set SelfCheckinTimeout to a small value like 5 >2 - Go to the self checkin: > http://localhost:8080/cgi-bin/koha/sci/sci-main.pl >3 - Sign in as the SCI user >4 - Watch the page, it should refresh every 5 seconds >5 - Confirm that moving the mouse/typing prevents the reset >6 - Check in an item >7 - Wait and confirm reload of main page happens when idle >8 - Confirm mouse/typing prevents refresh >9 - Sign off! > >Signed-off-by: David Nind <david@davidnind.com> >Signed-off-by: Martin Renvoize <martin.renvoize@openfifth.co.uk> >--- > .../bootstrap/en/modules/sci/sci-main.tt | 478 +++++++++--------- > koha-tmpl/opac-tmpl/bootstrap/js/timeout.js | 39 ++ > opac/sci/sci-main.pl | 4 - > 3 files changed, 269 insertions(+), 252 deletions(-) > create mode 100644 koha-tmpl/opac-tmpl/bootstrap/js/timeout.js > >diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/sci/sci-main.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/sci/sci-main.tt >index 7bfa4dd8c31..1572315c32e 100644 >--- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/sci/sci-main.tt >+++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/sci/sci-main.tt >@@ -25,11 +25,10 @@ > [% SET SelfCheckInMainUserBlock = AdditionalContents.get( location => "SelfCheckInMainUserBlock", lang => lang, library => logged_in_user.branchcode || default_branch, blocktitle => 0 ) %] > [% INCLUDE 'doc-head-open.inc' %] > [% SET OpacLangSelectorMode = Koha.Preference('OpacLangSelectorMode') %] >-<title>Self check-in › [% IF ( LibraryNameTitle ) %][% LibraryNameTitle | html %][% ELSE %]Koha[% END %]</title> >+<title>Self check-in › [% IF ( LibraryNameTitle ) %][% LibraryNameTitle | html %][% ELSE %]Koha [% END %]</title> > > <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> >-<meta name="generator" content="Koha" /> >-<!-- leave this for stats --> >+<meta name="generator" content="Koha" /> <!-- leave this for stats --> > > [% IF ( Koha.Preference('OpacFavicon') ) %] > <link rel="shortcut icon" href="[% Koha.Preference('OpacFavicon') | url %]" type="image/x-icon" /> >@@ -44,28 +43,16 @@ > [% ELSE %] > [% Asset.css("css/sco.css") | $raw %] > [% END %] >-[% IF ( Koha.Preference('OPACUserCSS') ) %] >- <!-- prettier-ignore-start --> >- <style>[% Koha.Preference('OPACUserCSS') | $raw %]</style> >- <!-- prettier-ignore-end --> >-[% END %] >+[% IF ( Koha.Preference('OPACUserCSS') ) %]<style>[% Koha.Preference('OPACUserCSS') | $raw %]</style>[% END %] > [% IF Branches.GetBranchSpecificCSS( Branches.GetLoggedInBranchcode() || default_branch) %] >- <!-- prettier-ignore-start --> > <style> > [% Branches.GetBranchSpecificCSS( Branches.GetLoggedInBranchcode() || default_branch) | $raw %] > </style> >- <!-- prettier-ignore-end --> >-[% END %] >-[% IF ( Koha.Preference('SelfCheckInUserCSS') ) %] >- <!-- prettier-ignore-start --> >- <style>[% Koha.Preference('SelfCheckInUserCSS') | $raw %]</style> >- <!-- prettier-ignore-end --> > [% END %] >+[% IF ( Koha.Preference('SelfCheckInUserCSS') ) %]<style>[% Koha.Preference('SelfCheckInUserCSS') | $raw %]</style>[% END %] > <script> > var Koha = {}; >- function _(s) { >- return s; >- } // dummy function for gettext >+ function _(s) { return s } // dummy function for gettext > </script> > [% IF lang && lang != 'en' %] > [% Asset.js(lang _ '/js/locale_data.js') | $raw %] >@@ -74,130 +61,127 @@ > [% Asset.js('js/i18n.js') | $raw %] > </head> > <body id="sci_main" class="sci"> >-[% INCLUDE 'masthead-sci.inc' %] >- >-<div class="main"> >- <div class="container-fluid"> >- <div class="row"> >- <div class="col order-first order-md-first order-lg-2"> >- <div id="masthead"> >- <h1>[% LibraryName | html %] Self check-in</h1> >- </div> >- >- [% IF ( nopermission ) %] >- [%# This is what is displayed if user doesn't have permission %] >- <div class="alert alert-warning"> >- <h2>Access denied</h2> >- <p>Sorry, this self check-in station has lost authentication. Please contact the administrator to resolve this problem.</p> >- </div> >- [% ELSIF ( different_ip ) %] >- [%# This is what is displayed if user doesn't have permission %] >- <div class="alert alert-warning"> >- <h2>Session lost</h2> >- <p>You are accessing self check-in from a different IP address! Please log in again.</p> >+ [% INCLUDE 'masthead-sci.inc' %] >+ >+ <div class="main"> >+ <div class="container-fluid"> >+ <div class="row"> >+ <div class="col order-first order-md-first order-lg-2"> >+ <div id="masthead"> >+ <h1>[% LibraryName | html %] Self check-in</h1> > </div> >- [% ELSIF ( checkins ) %] >- [%# We have results from a check-in attempt %] >- <div id="checkins" class="sci_results_list"> >- [% IF ( success && success.size > 0 || errors && errors.size > 0 ) %] >- <table id="sci_bcheckins_table" class="table table-bordered table-striped dataTable no-footer"> >- <caption><h2>Results</h2></caption> >- <thead> >- <th>Barcode</th> >- <th>Status</th> >- </thead> >- <tbody> >- [% FOREACH success_line IN success %] >- <tr> >- <td>[% success_line.barcode | html %]</td> >- <td>Checked in</td> >- </tr> >- [% END %] >- [% FOREACH error IN errors %] >- <tr> >- <td>[% error.barcode | html %]</td> >- <td>Not checked in [% PROCESS error_message messages=error.messages %]</td> >- </tr> >- [% END %] >- </tbody> >- </table> >- <div> >- <a href="/cgi-bin/koha/sci/sci-main.pl" id="sci_finish_button" class="btn btn-primary" role="button"><i class="fa fa-check" aria-hidden="true"></i> Finish</a> >- </div> >- [% ELSE %] >- <div class="alert alert-warning"> >- <p>Your request included no check-ins.</p> >- </div> >- [% END %] >- </div> >- [% ELSE %] >- [%# Prompt for barcodes %] >- <div id="new_checkins" class="sci_entry"> >- <form id="scan_form" name="scan_form" method="post" action="/cgi-bin/koha/sci/sci-main.pl"> >- [% INCLUDE 'csrf-token.inc' %] >- <legend class="sr-only">Scan</legend> >- <div class="row"> >- <div class="col-auto"> >- <label for="barcode_input">Scan the item or enter its barcode:</label> >- </div> >- <div class="col-4"> >- <input id="barcode_input" name="barcode_input" size="20" type="text" class="focus form-control" autocomplete="off" /> >- </div> >- <div class="col-auto"> >- <button id="sci_append_button" type="submit" class="btn btn-primary"> Submit </button> >+ >+ [% IF ( nopermission ) %] >+ [%# This is what is displayed if user doesn't have permission %] >+ <div class="alert alert-warning"> >+ <h2>Access denied</h2> >+ <p>Sorry, this self check-in station has lost authentication. Please contact the administrator to resolve this problem.</p> >+ </div> >+ [% ELSIF ( different_ip ) %] >+ [%# This is what is displayed if user doesn't have permission %] >+ <div class="alert alert-warning"> >+ <h2>Session lost</h2> >+ <p>You are accessing self check-in from a different IP address! Please log in again.</p> >+ </div> >+ [% ELSIF ( checkins ) %] >+ [%# We have results from a check-in attempt %] >+ <div id="checkins" class="sci_results_list"> >+ [% IF ( success && success.size > 0 || errors && errors.size > 0 ) %] >+ <table id="sci_bcheckins_table" class="table table-bordered table-striped dataTable no-footer"> >+ <caption><h2>Results</h2></caption> >+ <thead> >+ <th>Barcode</th> >+ <th>Status</th> >+ </thead> >+ <tbody> >+ [% FOREACH success_line IN success %] >+ <tr> >+ <td>[% success_line.barcode | html %]</td> >+ <td>Checked in</td> >+ </tr> >+ [% END %] >+ [% FOREACH error IN errors %] >+ <tr> >+ <td>[% error.barcode | html %]</td> >+ <td>Not checked in [% PROCESS error_message messages=error.messages %]</td> >+ </tr> >+ [% END %] >+ </tbody> >+ </table> >+ <div> >+ <a href="/cgi-bin/koha/sci/sci-main.pl" id="sci_finish_button" class="btn btn-primary" role="button"><i class="fa fa-check" aria-hidden="true"></i> Finish</a> >+ </div> >+ [% ELSE %] >+ <div class="alert alert-warning"> >+ <p>Your request included no check-ins.</p> > </div> >- </div> >- <!-- /.row --> >- <div class="row"> >- <div class="col"> >- <div class="sci_input_append"> >- <table id="sci_barcodes_table" class="table table-bordered table-striped dataTable no-footer" style="display: none;"> >- <caption class="sr-only">Barcodes</caption> >- <thead> >- <th class="barcodes_column">Barcode</th> >- </thead> >- <tbody> </tbody> >- </table> >- <!-- /#sci_barcodes_table --> >+ [% END %] >+ </div> >+ [% ELSE %] >+ [%# Prompt for barcodes %] >+ <div id="new_checkins" class="sci_entry"> >+ <form id="scan_form" name="scan_form" method="post" action="/cgi-bin/koha/sci/sci-main.pl"> >+ [% INCLUDE 'csrf-token.inc' %] >+ <legend class="sr-only">Scan</legend> >+ <div class="row"> >+ <div class="col-auto"> >+ <label for="barcode_input">Scan the item or enter its barcode:</label> > </div> >- <!-- .sci_input_append --> >- <input type="hidden" name="op" value="cud-check_in" /> >- <div id="button_bar"> >- <button id="sci_checkin_button" type="submit" class="btn btn-primary" style="display: none;"> <i class="fa fa-check-square" aria-hidden="true"></i> <span>Check in</span> </button> >- <a href="/cgi-bin/koha/sci/sci-main.pl" class="btn btn-danger" role="button" style="display: none;" id="sci_refresh_button"> <i class="fa fa-times" aria-hidden="true"></i> <span>Cancel</span> </a> >+ <div class="col-4"> >+ <input id="barcode_input" name="barcode_input" size="20" type="text" class="focus form-control" autocomplete="off" /> > </div> >- <!-- /#button_bar --> >- </div> >- <!-- /.col --> >- </div> >- <!-- /.row --> >- </form> >- <!-- /#scan_form --> >- </div> >- <!-- / #new_checkins --> >- [% END # /IF ( nopermission ) %] >- >- [% IF ( SelfCheckInMainUserBlock ) %] >- <div id="scimainuserblock"> [% PROCESS koha_news_block news => SelfCheckInMainUserBlock %] </div> >- [% END %] >- </div> >- <!-- / #masthead --> >- </div> >- <!-- / .row --> >- </div> >- <!-- / .container-fluid --> >-</div> >-<!-- / .main --> >- >-[%# Help modal %] >+ <div class="col-auto"> >+ <button id="sci_append_button" type="submit" class="btn btn-primary"> >+ Submit >+ </button> >+ </div> >+ </div> <!-- /.row --> >+ <div class="row"> >+ <div class="col"> >+ <div class="sci_input_append"> >+ <table id="sci_barcodes_table" class="table table-bordered table-striped dataTable no-footer" style="display: none;"> >+ <caption class="sr-only">Barcodes</caption> >+ <thead> >+ <th class="barcodes_column">Barcode</th> >+ </thead> >+ <tbody> >+ </tbody> >+ </table> <!-- /#sci_barcodes_table --> >+ </div> <!-- .sci_input_append --> >+ <input type="hidden" name="op" value="cud-check_in" /> >+ <div id="button_bar"> >+ <button id="sci_checkin_button" type="submit" class="btn btn-primary" style="display: none;"> >+ <i class="fa fa-check-square" aria-hidden="true"></i> <span>Check in</span> >+ </button> >+ <a href="/cgi-bin/koha/sci/sci-main.pl" class="btn btn-danger" role="button" style="display: none;" id="sci_refresh_button"> >+ <i class="fa fa-times" aria-hidden="true"></i> <span>Cancel</span> >+ </a> >+ </div> <!-- /#button_bar --> >+ </div> <!-- /.col --> >+ </div> <!-- /.row --> >+ </form> <!-- /#scan_form --> >+ </div> <!-- / #new_checkins --> >+ [% END # /IF ( nopermission ) %] >+ >+ [% IF ( SelfCheckInMainUserBlock ) %] >+ <div id="scimainuserblock"> >+ [% PROCESS koha_news_block news => SelfCheckInMainUserBlock %] >+ </div> >+ [% END %] >+ </div> <!-- / #masthead --> >+ </div> <!-- / .row --> >+ </div> <!-- / .container-fluid --> >+ >+</div> <!-- / .main --> >+ >+[% # Help modal %] > <div id="helpModal" class="modal" tabindex="-1" role="dialog" aria-labelledby="helpModalLabel" aria-hidden="true"> > <div class="modal-dialog"> > <div class="modal-content"> > <div class="modal-header"> > <h1 class="modal-title" id="helpModalLabel">Self check-in help</h1> > <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> >- </div> >- <!-- /.modal-header --> >+ </div> <!-- /.modal-header --> > <div class="modal-body"> > <ul> > <li>Scan each item or enter its barcode. A list with the entered barcodes will be displayed.</li> >@@ -206,139 +190,137 @@ > <li>The 'Finish' button is presented to start over.</li> > <li>At any step, clicking the 'Cancel' button will erase the scanned barcodes and start over.</li> > </ul> >- </div> >- <!-- /.modal-body --> >+ </div> <!-- /.modal-body --> > <div class="modal-footer"> > <button type="button" class="btn btn-primary" data-bs-dismiss="modal">Close</button> > </div> >- </div> >- <!-- /.modal-content --> >- </div> >- <!-- /.modal-dialog --> >-</div> >-<!-- /#helpModal --> >+ </div> <!-- /.modal-content --> >+ </div> <!-- /.modal-dialog --> >+</div> <!-- /#helpModal --> > > </body> > > [% INCLUDE 'opac-bottom.inc' %] > [% BLOCK jsinclude %] >+[% Asset.js('js/timeout.js') | $raw %] > <script> >+ > function mungeHistory() { >- // prevent back button from allowing form resubmission >- if (history && history.pushState) { >- history.replaceState(null, document.title, window.location.href); >- } >- } >- >- function dofocus() { >- $(".focus:last").select(); >- } >- >- var barcodes = []; >- >- $( window ).on( "load", function() { >- dofocus(); >- }); >- >- $( window ).on( "unload", function() { >- mungeHistory(); >- }); >- >- $(document).ready(function() { >- // Barcodes scanning table initially hidden >- $("#sci_barcodes_table").hide(); >- >- // Control the 'append' button behaviour >- $("#sci_append_button").on('click', function(e) { >- // Make sure the form is not submitted by the button >- e.preventDefault(); >- >- var barcode = $('#barcode_input').val().trim(); // Trim whitespace from input >- >- if (barcode !== "") { >- // Properly escape the barcode value by using .text() for display >- var barcodeHtml = $('<tr style="font-size: initial;"><td></td></tr>'); >- barcodeHtml.find('td').text(barcode).append( >- $('<input>').attr({ >- type: 'hidden', >- name: 'barcode', >- value: barcode >- }) >- ); >- >- $('#sci_barcodes_table tbody').append(barcodeHtml); >- >- // Make sure the table is now displayed >- $("#sci_barcodes_table").show(); >- $('#sci_checkin_button').show(); >- $('#sci_refresh_button').show(); >- >- // Add barcode to the array >- barcodes.push(barcode); >- } >- >- // Clear the input and reset the focus >- $('#barcode_input').val(''); >- dofocus(); >- }); >+ // prevent back button from allowing form resubmission >+ if (history && history.pushState) { >+ history.replaceState(null, document.title, window.location.href); >+ } >+ } > >- $(".helpModal-trigger").on("click", function(e) { >- e.preventDefault(); >- $("#helpModal").modal("show"); >- }); >+ function dofocus() { >+ $(".focus:last").select(); >+ } > >- // Set focus at the beginning >+ var barcodes = []; >+ >+ $( window ).on( "load", function() { > dofocus(); > }); > >- >- var idleTime = 0; >- $(document).ready(function () { >- //Increment the idle time counter every second >- var idleInterval = setInterval(timerIncrement, 1000); >- >- //Zero the idle timer on mouse movement. >- $(this).mousemove(function (e) { >- idleTime = 0; >- }); >- $(this).keypress(function (e) { >- idleTime = 0; >- }); >- }); >- >- function timerIncrement() { >- if ( $("#sci_finish_button").is(":visible") || $("#sci_refresh_button").is(":visible") ) { >- idleTime = idleTime + 1; >- idleTimeout = [% refresh_timeout | html %]; >- if (idleTime >= idleTimeout ) { >- location.href = '/cgi-bin/koha/sci/sci-main.pl'; >- } >- } >- } >- >- >- function checkBarcodeInput() { >- var inputField = document.getElementById("barcode_input"); >- var button = document.getElementById("sci_append_button"); >- >- if (inputField.value.trim() === "") { >- button.disabled = true; >- } else { >- button.disabled = false; >- } >- } >- >- checkBarcodeInput(); >- >- document.getElementById("barcode_input").addEventListener("input", checkBarcodeInput); >- >- document.getElementById("sci_append_button").addEventListener("click", function() { >- setTimeout(checkBarcodeInput, 10); >+ $( window ).on( "unload", function() { >+ mungeHistory(); > }); >+ >+ $(document).ready(function() { >+ // Barcodes scanning table initially hidden >+ $("#sci_barcodes_table").hide(); >+ >+ // Control the 'append' button behaviour >+ $("#sci_append_button").on('click', function(e) { >+ // Make sure the form is not submitted by the button >+ e.preventDefault(); >+ >+ var barcode = $('#barcode_input').val().trim(); // Trim whitespace from input >+ >+ if (barcode !== "") { >+ // Properly escape the barcode value by using .text() for display >+ var barcodeHtml = $('<tr style="font-size: initial;"><td></td></tr>'); >+ barcodeHtml.find('td').text(barcode).append( >+ $('<input>').attr({ >+ type: 'hidden', >+ name: 'barcode', >+ value: barcode >+ }) >+ ); >+ >+ $('#sci_barcodes_table tbody').append(barcodeHtml); >+ >+ // Make sure the table is now displayed >+ $("#sci_barcodes_table").show(); >+ $('#sci_checkin_button').show(); >+ $('#sci_refresh_button').show(); >+ login_timeout(); >+ >+ // Add barcode to the array >+ barcodes.push(barcode); >+ } >+ >+ // Clear the input and reset the focus >+ $('#barcode_input').val(''); >+ dofocus(); >+ }); >+ >+ $(".helpModal-trigger").on("click", function(e) { >+ e.preventDefault(); >+ $("#helpModal").modal("show"); >+ }); >+ >+ // Set focus at the beginning >+ dofocus(); >+}); >+ >+ document.addEventListener("DOMContentLoaded",function(){ >+ if ( document.querySelector('#sci_finish_button,#sci_append_button') ){ >+ login_timeout(); >+ } >+ }); >+ function login_timeout(){ >+ //NOTE: There can only be 1 sci_login_timer at a time >+ if ( ! window.sci_login_timer ){ >+ const idleTimeout = "[% Koha.Preference('SelfCheckInTimeOut') || 120 | html %]"; >+ const home_href = "/cgi-bin/koha/sci/sci-main.pl"; >+ const sci_timer = new sc_timer({ >+ "idle_timeout": idleTimeout, >+ "redirect_url": home_href >+ }); >+ window.sci_login_timer = sci_timer; >+ sci_timer.start_timer(); >+ } >+ } >+ >+ function checkBarcodeInput() { >+ var inputField = document.getElementById("barcode_input"); >+ var button = document.getElementById("sci_append_button"); >+ >+ if (inputField.value.trim() === "") { >+ button.disabled = true; >+ } else { >+ button.disabled = false; >+ } >+ } >+ >+checkBarcodeInput(); >+ >+document.getElementById("barcode_input").addEventListener("input", checkBarcodeInput); >+ >+document.getElementById("sci_append_button").addEventListener("click", function() { >+ setTimeout(checkBarcodeInput, 10); >+}); >+ >+ >+ >+ >+ >+ >+ > </script> >+ > [% IF ( Koha.Preference('SelfCheckInUserJS') ) %] >- <script> >- [% Koha.Preference('SelfCheckInUserJS') | $raw %] >- </script> >+ <script>[% Koha.Preference('SelfCheckInUserJS') | $raw %]</script> > [% END %] > [% END %] >diff --git a/koha-tmpl/opac-tmpl/bootstrap/js/timeout.js b/koha-tmpl/opac-tmpl/bootstrap/js/timeout.js >new file mode 100644 >index 00000000000..6c508d14c07 >--- /dev/null >+++ b/koha-tmpl/opac-tmpl/bootstrap/js/timeout.js >@@ -0,0 +1,39 @@ >+class sc_timer { >+ constructor(args) { >+ const idle_timeout = args["idle_timeout"]; >+ const redirect_url = args["redirect_url"]; >+ if (idle_timeout) { >+ this.idle_timeout = idle_timeout; >+ } >+ if (redirect_url) { >+ this.redirect_url = redirect_url; >+ } >+ this.idle_time = 0; >+ } >+ >+ start_timer() { >+ const self = this; >+ //Increment the idle time counter every 1 second >+ const idle_interval = setInterval(function () { >+ self._timer_increment(); >+ }, 1000); >+ >+ document.addEventListener("mousemove", function () { >+ self.reset_timer(); >+ }); >+ document.addEventListener("keypress", function () { >+ self.reset_timer(); >+ }); >+ } >+ >+ reset_timer() { >+ this.idle_time = 0; >+ } >+ >+ _timer_increment() { >+ this.idle_time++; >+ if (this.idle_time >= this.idle_timeout) { >+ location.href = this.redirect_url; >+ } >+ } >+} >diff --git a/opac/sci/sci-main.pl b/opac/sci/sci-main.pl >index 34d433a53fc..d254347b5be 100755 >--- a/opac/sci/sci-main.pl >+++ b/opac/sci/sci-main.pl >@@ -102,8 +102,4 @@ if ( $op eq 'cud-check_in' ) { > $template->param( success => \@success, errors => \@errors, checkins => 1 ); > } > >-# Make sure timeout has a reasonable value >-my $timeout = C4::Context->preference('SelfCheckInTimeout') || 120; >-$template->param( refresh_timeout => $timeout ); >- > output_html_with_http_headers $cgi, $cookie, $template->output, undef, { force_no_caching => 1 }; >-- >2.49.0
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 36586
:
175210
|
175239
|
175919
|
181645
|
181646
|
181694
|
181695
|
181874
|
181875
|
182022
|
182023