Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Raw javascript rendered on back button click

Our team was recently presented an initially edge-case issue in which a User would be displayed raw javascript when clicking the browser's back button from a page that had some sort of javascript rendering (be it Ajax, tabs, etc.). To re-create, we followed the following steps:

  • Visit the users job applications index
  • Click a button to the manage job posting page
  • Click on a tab (using bettertabs gem)
  • Click the browser's back button

The previous steps would display:

(function() {

  $(".job_applications").html("<li class=\"job_posting_application\">\n
    ...
    ...
    ...
    ...
  );

}).call(this);

In some hit-or-miss cases, you wouldn't need to click on a tab prior to going back to the previous page, yet it would still render the raw javascript. At the end of the day, it seems the last rendered template is being cached, which is normal and expected on the browser's part, but leads into what I believe to be a larger problem.

The Rails Guides state in the section on Layouts and Rendering, specifically regarding MIME type of the template:

By default, Rails will serve the results of a rendering operation with the MIME content-type of text/html (or application/json if you use the :json option, or application/xml for the :xml option.).

Based on the Rails defaults, our controller's index action is expected to render our index.html.slim template. However, when making a non-remote call to that page (e.g., directly navigating to the page in the browser) while tailing the server logs, we notice that it actually renders index.js.coffee. Below is our controller action, and notice we are not explicitly responding to html or js formats, as we probably should considering the overlying features in this page:

def index
  @company_id, @division_id, @job_posting_id = params[:company_id], params[:division_id], params[:job_posting_id]

  # API requests are made here to instantiate @job_posting, et al.,
  # but are not shown for brevity

  authorize! :manage, @job_posting

  @survey = @job_posting.survey
  @job_applications = @job_posting.job_applications(sort_column, sort_direction)
end

Given this setup, however, index.html.slim should be rendered based on Rails defaults. When adding a respond_to block, it seems the caching is still in effect and the controller could care less about the presence of a respond_to block:

def index
  ...
  ...
  respond_to do |format|
    format.html
    format.js
  end
end

Even when explicitly, and albeit smelly, telling each format to render a different template, it seems the js.coffee template takes precedence over the html.slim template:

def index
  ...
  ...
  respond_to do |format|
    format.html { render template: "users/job_posting_applications/index" }
    format.js { render template: "users/job_posting_applications/ajax" }
  end
end

In the above case, directly navigating to the page in the browser (in other words, not making a remote Ajax call), the server log would render ajax.js.coffee, even though the Rails default is html unless otherwise specified.

All this being said, here are some other findings:

Started GET "/users/companies/1/divisions/18/job_postings/349421/applications" for 127.0.0.1 at 2012-10-03 19:55:26 -0400
Processing by Users::JobPostingApplicationsController#index as JSON

(you can reference the entire request shown above in this pastie)

Why it's processing as JSON is beyond me, considering we are not serving any JSON on this request, and have no specifications in routing for a default format of :json for this route.

Additionally, when debugging the value of request.format within this action, it returns application/json.

Another scenario that presented is within another controller (users/company_admin_metrics#index) that contains only an index.html.slim template. When navigating to this page, the server log shows that it rendered users/company_admin_metrics/index.html.slim within layouts/users. When I create a blank js.coffee template:

$ touch app/views/users/company_admin_metrics/index.js.coffee

and directly navigate to that index page, the server log shows that it rendered users/company_admin_metrics/index.js.coffee, which further exposes a potential issue somewhere regarding template rendering precedence.

Has anyone encountered a similar issue that may provide a potential fix for this?

Our stack

Below is a minute list of the basic players into this particular question:

  • Rails 3.2.6
  • Coffee-Rails 3.2.1
  • Bettertabs 1.2.6

This request depends on requests to our job posting API through a client gem that parses JSON and returns a Ruby object, but these are not coupled with this particular app in such a way that would conflict and cause this app to have a content-type of application/json for such a request as described above.

like image 671
Steve Tipton Avatar asked Oct 04 '12 15:10

Steve Tipton


3 Answers

I think you need to append the format extension .js to the URL in your ajax request.

What is probably happening is that the user is hitting the index action at /job_applications with a GET request for html. Then the ajax is hitting the same URL/action but requesting javascript. When the user clicks the back button, the browser can't tell the difference between the two so uses the most recent (the javascript response).

like image 186
jeffcarbs Avatar answered Oct 14 '22 08:10

jeffcarbs


Started seeing this exact issue recently in Chrome with Rails 4.2.4. Appears to be a known and hotly debated issue. Our team solved it by adding the Vary header to xhr requests:

class ApplicationController < ActionController::Base
  after_action :set_vary_header

  private

  # Fix a bug/issue/by-design(?) of browsers that have a hard time understanding
  # what to do about our ajax search page. This header tells browsers to not cache
  # the current contents of the page because previously when someone filtered results,
  # went to the listing's external site, then hit Back, they'd only see the last
  # ajax response snippet, not the full listings page for their search.
  #
  # Heated multi-year discussion on this issue in Chrome
  # https://code.google.com/p/chromium/issues/detail?id=94369
  #
  # And continued by the Rails team:
  # https://github.com/rails/jquery-ujs/issues/318
  # https://github.com/rails/jquery-rails/issues/121
  def set_vary_header
    if request.xhr?
      response.headers["Vary"] = "accept"
    end
  end
end
like image 7
Josh Kovach Avatar answered Oct 14 '22 09:10

Josh Kovach


It has been several months since this question has been asked but Ill chime in and give my two cents since I came across it.

Just as carbo said the browser can tell the difference between html and js request so when clicking the back button it sees the same url and just serves up the cached data which happens to the the js code. I had the some problem but with using push state javascript for modifying the browser history for ajax requests. The simplest solution I found was to tell the browser not to cache the request. So that when going back to the previous page its forced to reload it.

You can disable caching globally: $.ajaxSetup({ cache: false });

Or you can try specify cache false on specific ajax request.

like image 4
Talk2MeGooseman Avatar answered Oct 14 '22 09:10

Talk2MeGooseman