Fix the issue where firefox won't delete the access code

fixes CNVS-24622

test plan:
use case 1
- make a quiz with multiple attempts and an access code
- enter the access code, take and submit the quiz
- attempt to take the quiz again
- notice that the access code is requested again
use case 2
- make a quiz with multiple attempts and an access code
- enter the access code, and start the quiz
- navigate away from the quiz
- resume the quiz
- notice that the access code is requested again

Check on all supported browsers, each browser treats this issue
slightly differently. It never can be easy can it?

Change-Id: I31ade2501fc20945b8ff13f7f5a5071aca6d896e
Reviewed-on: https://gerrit.instructure.com/73582
Tested-by: Jenkins
Reviewed-by: Matt Berns <mberns@instructure.com>
QA-Review: Michael Hargiss <mhargiss@instructure.com>
Product-Review: Jason Sparks <jsparks@instructure.com>
This commit is contained in:
Davis McClellan 2016-03-02 14:41:12 -07:00
parent 1415d7bc7d
commit 42e96465ab
4 changed files with 111 additions and 104 deletions

View File

@ -314,6 +314,7 @@ CanvasRails::Application.routes.draw do
resources :quiz_submissions, controller: 'quizzes/quiz_submissions', path: :submissions do
collection do
put :backup
post :backup
end
member do
get :record_answer

View File

@ -84,7 +84,8 @@ define([
finalSubmitButtonClicked: false,
clockInterval: 500,
backupsDisabled: document.location.search.search(/backup=false/) > -1,
updateSubmission: function(repeat, beforeLeave, autoInterval, windowUnload) {
clearAccessCode: false,
updateSubmission: function(repeat, autoInterval) {
/**
* Transient: CNVS-9844
* Disable auto-backups if backup=true was passed as a query parameter.
@ -97,10 +98,10 @@ define([
if(quizSubmission.submitting && !repeat) { return; }
var now = new Date();
if((now - quizSubmission.lastSubmissionUpdate) < 1000 && !autoInterval) {
if(!autoInterval && (now - quizSubmission.lastSubmissionUpdate) < 1000) {
return;
}
if(quizSubmission.currentlyBackingUp && !windowUnload) { return; }
if(quizSubmission.currentlyBackingUp) { return; }
quizSubmission.currentlyBackingUp = true;
quizSubmission.lastSubmissionUpdate = new Date();
@ -113,97 +114,82 @@ define([
$lastSaved.text(I18n.t('saving', 'Saving...'));
var url = $(".backup_quiz_submission_url").attr('href');
// If called before leaving the page (ie. onbeforeunload), we can't use any async or FF will kill the PUT request.
data.leaving = !!windowUnload && !this.oneAtATime;
if (beforeLeave){
$.flashMessage(I18n.t('saving', 'Saving...'));
$.ajax({
url: url,
data: data,
type: 'PUT',
dataType: 'json',
async: false // NOTE: Not asynchronous. Otherwise Firefox will cancel the request as navigating away from the page.
// NOTE: No callbacks. Don't care about response. Just making effort to save the quiz
});
// since this is sync, a callback never fires to reset this
quizSubmission.currentlyBackingUp = false;
}
else {
(function(submissionData) {
// Need a shallow clone of the data here because $.ajaxJSON modifies in place
var thisSubmissionData = _.clone(submissionData);
// If this is a timeout-based submission and the data is the same as last time,
// palliate the server by skipping the data submission
if (!quizSubmission.inBackground && repeat && _.isEqual(submissionData, lastSuccessfulSubmissionData)) {
$lastSaved.text(I18n.t('saving_not_needed', "No new data to save. Last checked at %{t}", { t: $.friendlyDatetime(new Date()) }));
(function(submissionData) {
// Need a shallow clone of the data here because $.ajaxJSON modifies in place
var thisSubmissionData = _.clone(submissionData);
// If this is a timeout-based submission and the data is the same as last time,
// palliate the server by skipping the data submission
if (!quizSubmission.inBackground && repeat && _.isEqual(submissionData, lastSuccessfulSubmissionData)) {
$lastSaved.text(I18n.t('saving_not_needed', "No new data to save. Last checked at %{t}", { t: $.friendlyDatetime(new Date()) }));
quizSubmission.currentlyBackingUp = false;
setTimeout(function() { quizSubmission.updateSubmission(true, true) }, 30000);
return;
}
$.ajaxJSON(url, 'PUT', submissionData,
// Success callback
function(data) {
lastSuccessfulSubmissionData = thisSubmissionData;
$lastSaved.text(I18n.t('saved_at', 'Quiz saved at %{t}', { t: $.friendlyDatetime(new Date()) }));
quizSubmission.currentlyBackingUp = false;
quizSubmission.inBackground = false;
if(repeat) {
setTimeout(function() {quizSubmission.updateSubmission(true, true) }, 30000);
}
if(data && data.end_at) {
var endAtFromServer = Date.parse(data.end_at),
submissionEndAt = Date.parse(endAt.text()),
serverEndAtTime = endAtFromServer.getTime(),
submissionEndAtTime = submissionEndAt.getTime();
quizSubmission.timeLeft = data.time_left * 1000;
// if the new endAt from the server is different than our current endAt, then notify
// the user that their time limit's changed and let updateTime do the rest.
if (serverEndAtTime !== submissionEndAtTime) {
if(serverEndAtTime > submissionEndAtTime) {
$.flashMessage(I18n.t('You have been given extra time on this attempt'))
} else {
$.flashMessage(I18n.t('Your time for this quiz has been reduced.'));
}
quizSubmission.endAt.text(data.end_at);
quizSubmission.endAtParsed = endAtFromServer;
}
}
},
// Error callback
function(resp, ec) {
quizSubmission.currentlyBackingUp = false;
setTimeout(function() { quizSubmission.updateSubmission(true, false, true) }, 30000);
return;
}
$.ajaxJSON(url, 'PUT', submissionData,
// Success callback
function(data) {
lastSuccessfulSubmissionData = thisSubmissionData;
$lastSaved.text(I18n.t('saved_at', 'Quiz saved at %{t}', { t: $.friendlyDatetime(new Date()) }));
quizSubmission.currentlyBackingUp = false;
quizSubmission.inBackground = false;
if(repeat) {
setTimeout(function() {quizSubmission.updateSubmission(true, false, true) }, 30000);
}
if(data && data.end_at) {
var endAtFromServer = Date.parse(data.end_at),
submissionEndAt = Date.parse(endAt.text()),
serverEndAtTime = endAtFromServer.getTime(),
submissionEndAtTime = submissionEndAt.getTime();
quizSubmission.timeLeft = data.time_left * 1000;
// if the new endAt from the server is different than our current endAt, then notify
// the user that their time limit's changed and let updateTime do the rest.
if (serverEndAtTime !== submissionEndAtTime) {
serverEndAtTime > submissionEndAtTime ?
$.flashMessage(I18n.t('notices.extra_time', 'You have been given extra time on this attempt')) :
$.flashMessage(I18n.t('notices.less_time', 'Your time for this quiz has been reduced.'));
quizSubmission.endAt.text(data.end_at);
quizSubmission.endAtParsed = endAtFromServer;
}
}
},
// Error callback
function(resp, ec) {
quizSubmission.currentlyBackingUp = false;
// has the user logged out?
// TODO: support this redirect in LDB, by getting out of high security mode.
if (ec.status === 401 || resp['status'] == 'unauthorized') {
showDeauthorizedDialog();
}
else {
// Connectivity lost?
var current_user_id = window.ENV.current_user_id || "none";
$.ajaxJSON(
location.protocol + '//' + location.host + "/simple_response.json?user_id=" + current_user_id + "&rnd=" + Math.round(Math.random() * 9999999),
'GET', {},
function() {},
function() {
$.flashError(I18n.t('errors.connection_lost', "Connection to %{host} was lost. Please make sure you're connected to the Internet before continuing.", {'host': location.host}));
}
);
}
if(repeat) {
setTimeout(function() {quizSubmission.updateSubmission(true) }, 30000);
}
},
{
timeout: 15000
// has the user logged out?
// TODO: support this redirect in LDB, by getting out of high security mode.
if (ec.status === 401 || resp['status'] == 'unauthorized') {
showDeauthorizedDialog();
}
);
})(data);
}
else {
// Connectivity lost?
var current_user_id = window.ENV.current_user_id || "none";
$.ajaxJSON(
location.protocol + '//' + location.host + "/simple_response.json?user_id=" + current_user_id + "&rnd=" + Math.round(Math.random() * 9999999),
'GET', {},
function() {},
function() {
$.flashError(I18n.t('errors.connection_lost', "Connection to %{host} was lost. Please make sure you're connected to the Internet before continuing.", {'host': location.host}));
}
);
}
if(repeat) {
setTimeout(function() {quizSubmission.updateSubmission(true) }, 30000);
}
},
{
timeout: 15000
}
);
})(data);
},
updateTime: function() {
@ -409,15 +395,41 @@ define([
window.onbeforeunload = function(e) {
if (!quizSubmission.navigatingToRelogin) {
quizSubmission.updateSubmission(false, true, false, true);
if(!quizSubmission.submitting && !quizSubmission.alreadyAcceptedNavigatingAway && !unloadWarned) {
quizSubmission.clearAccessCode = true
setTimeout(function() { unloadWarned = false; }, 0);
unloadWarned = true;
return I18n.t('confirms.unfinished_quiz', "You're about to leave the quiz unfinished. Continue anyway?");
}
}
};
window.addEventListener('unload', function(e) {
var data = $("#submit_quiz_form").getFormData();
var url = $(".backup_quiz_submission_url").attr('href');
data.leaving = !!quizSubmission.clearAccessCode;
if(navigator.sendBeacon){
var blob = new Blob([JSON.stringify(data)], {type : 'application/json; charset=utf-8'});
navigator.sendBeacon(url, blob);
}
else {
$.flashMessage(I18n.t('Saving...'));
$.ajax({
url: url,
data: data,
type: 'POST',
dataType: 'json',
async: false
});
}
// since this is sync, a callback never fires to reset this
quizSubmission.currentlyBackingUp = false;
},
false)
$(document).delegate('a', 'click', function(event) {
quizSubmission.clearAccessCode = true
if($(this).closest('.ui-dialog,.mceToolbar,.ui-selectmenu').length > 0) { return; }
if($(this).hasClass('no-warning')) {
@ -700,9 +712,7 @@ define([
// set the form action depending on the button clicked
$submit_buttons.click(function(event) {
// call updateSubmission with beforeLeave=true so quiz is saved synchronously
quizSubmission.updateSubmission(false, true);
quizSubmission.clearAccessCode = false;
var action = $(this).data('action');
if(action != undefined) {
$('#submit_quiz_form').attr('action', action);

View File

@ -120,12 +120,6 @@ describe 'quizzes' do
end
expect_new_page_load { submit_dialog('#deauthorized_dialog') }
# log back in
login_as(@pseudonym.unique_id, @pseudonym.password)
# we should be back at the quiz show page
expect(fln('Resume Quiz')).to be_present
end
end
end

View File

@ -105,8 +105,12 @@ describe 'taking a quiz' do
ensure
# This prevents selenium from freezing when the dialog appears upon leaving the quiz
fln('Quizzes').click
driver.switch_to.alert.accept
begin
fln('Quizzes').click
driver.switch_to.alert.accept
rescue Selenium::WebDriver::Error::NoAlertOpenError
# Do nothing
end
end
def verify_access_code_prompt
@ -114,7 +118,6 @@ describe 'taking a quiz' do
end
it 'prompts for access code upon resuming the quiz', priority: "1", test_id: 421218 do
skip('Known issue CNVS-24622')
start_and_exit_quiz do
expect_new_page_load { fj('a.ig-title', '#assignment-quizzes').click }
expect_new_page_load { fln('Resume Quiz').click }
@ -123,7 +126,6 @@ describe 'taking a quiz' do
end
it 'prompts for an access code upon resuming the quiz via the browser back button', priority: "1", test_id: 421222 do
skip('Known issue CNVS-24622')
start_and_exit_quiz do
expect_new_page_load { driver.navigate.back }
verify_access_code_prompt