aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTimmy Willison <timmywillisn@gmail.com>2016-03-15 18:15:02 -0400
committerTimmy Willison <timmywillisn@gmail.com>2016-03-17 12:27:12 -0400
commite7af951addbf140e426584642fa02e9be58680ec (patch)
tree4b7cdd719bae2d14ebc96c1e168a772540285861
parent0ef97b59396f5c75e995d754cb8fbf81d3dab82c (diff)
downloadjquery-e7af951addbf140e426584642fa02e9be58680ec.tar.gz
jquery-e7af951addbf140e426584642fa02e9be58680ec.zip
Attributes: strip/collapse whitespace for set values on selects
Fixes gh-2978 Close gh-3002
-rw-r--r--src/attributes/val.js17
-rw-r--r--test/unit/attributes.js82
2 files changed, 88 insertions, 11 deletions
diff --git a/src/attributes/val.js b/src/attributes/val.js
index 9999d985b..1fa91713d 100644
--- a/src/attributes/val.js
+++ b/src/attributes/val.js
@@ -4,7 +4,8 @@ define( [
"../core/init"
], function( jQuery, support ) {
-var rreturn = /\r/g;
+var rreturn = /\r/g,
+ rspaces = /[\x20\t\r\n\f]+/g;
jQuery.fn.extend( {
val: function( value ) {
@@ -80,9 +81,15 @@ jQuery.extend( {
option: {
get: function( elem ) {
- // Support: IE<11
- // option.value not trimmed (#14858)
- return jQuery.trim( elem.value );
+ var val = jQuery.find.attr( elem, "value" );
+ return val != null ?
+ val :
+
+ // Support: IE10-11+
+ // option.text throws exceptions (#14686, #14858)
+ // Strip and collapse whitespace
+ // https://html.spec.whatwg.org/#strip-and-collapse-whitespace
+ jQuery.trim( jQuery.text( elem ) ).replace( rspaces, " " );
}
},
select: {
@@ -135,7 +142,7 @@ jQuery.extend( {
while ( i-- ) {
option = options[ i ];
if ( option.selected =
- jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1
+ jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1
) {
optionSet = true;
}
diff --git a/test/unit/attributes.js b/test/unit/attributes.js
index e52e85b49..3a6f93403 100644
--- a/test/unit/attributes.js
+++ b/test/unit/attributes.js
@@ -1095,6 +1095,71 @@ QUnit.test( "val(select) after form.reset() (Bug #2551)", function( assert ) {
jQuery( "#kk" ).remove();
} );
+QUnit.test( "select.val(space characters) (gh-2978)", function( assert ) {
+ assert.expect( 35 );
+
+ var $select = jQuery( "<select/>" ).appendTo( "#qunit-fixture" ),
+ spaces = {
+ "\\t": {
+ html: "&#09;",
+ val: "\t"
+ },
+ "\\n": {
+ html: "&#10;",
+ val: "\n"
+ },
+ "\\r": {
+ html: "&#13;",
+ val: "\r"
+ },
+ "\\f": "\f",
+ "space": " ",
+ "\\u00a0": "\u00a0",
+ "\\u1680": "\u1680"
+ },
+ html = "";
+ jQuery.each( spaces, function( key, obj ) {
+ var value = obj.html || obj;
+ html += "<option value='attr" + value + "'></option>";
+ html += "<option value='at" + value + "tr'></option>";
+ html += "<option value='" + value + "attr'></option>";
+ } );
+ $select.html( html );
+
+ jQuery.each( spaces, function( key, obj ) {
+ var val = obj.val || obj;
+ $select.val( "attr" + val );
+ assert.equal( $select.val(), "attr" + val, "Value ending with space character (" + key + ") selected (attr)" );
+
+ $select.val( "at" + val + "tr" );
+ assert.equal( $select.val(), "at" + val + "tr", "Value with space character (" + key + ") in the middle selected (attr)" );
+
+ $select.val( val + "attr" );
+ assert.equal( $select.val(), val + "attr", "Value starting with space character (" + key + ") selected (attr)" );
+ } );
+
+ jQuery.each( spaces, function( key, obj ) {
+ var value = obj.html || obj,
+ val = obj.val || obj;
+ html = "";
+ html += "<option>text" + value + "</option>";
+ html += "<option>te" + value + "xt</option>";
+ html += "<option>" + value + "text</option>";
+ $select.html( html );
+
+ $select.val( "text" );
+ assert.equal( $select.val(), "text", "Value with space character at beginning or end is stripped (" + key + ") selected (text)" );
+
+ if ( /^\\u/.test( key ) ) {
+ $select.val( "te" + val + "xt" );
+ assert.equal( $select.val(), "te" + val + "xt", "Value with non-space whitespace character (" + key + ") in the middle selected (text)" );
+ } else {
+ $select.val( "te xt" );
+ assert.equal( $select.val(), "te xt", "Value with space character (" + key + ") in the middle selected (text)" );
+ }
+ } );
+} );
+
var testAddClass = function( valueObj, assert ) {
assert.expect( 9 );
@@ -1511,17 +1576,22 @@ QUnit.test( "option value not trimmed when setting via parent select", function(
assert.equal( jQuery( "<select><option> 2</option></select>" ).val( "2" ).val(), "2" );
} );
-QUnit.test( "Insignificant white space returned for $(option).val() (#14858)", function( assert ) {
- assert.expect( 3 );
+QUnit.test( "Insignificant white space returned for $(option).val() (#14858, gh-2978)", function( assert ) {
+ assert.expect( 16 );
var val = jQuery( "<option></option>" ).val();
assert.equal( val.length, 0, "Empty option should have no value" );
- val = jQuery( "<option> </option>" ).val();
- assert.equal( val.length, 0, "insignificant white-space returned for value" );
+ jQuery.each( [ " ", "\n", "\t", "\f", "\r" ], function( i, character ) {
+ var val = jQuery( "<option>" + character + "</option>" ).val();
+ assert.equal( val.length, 0, "insignificant white-space returned for value" );
+
+ val = jQuery( "<option>" + character + "test" + character + "</option>" ).val();
+ assert.equal( val.length, 4, "insignificant white-space returned for value" );
- val = jQuery( "<option> test </option>" ).val();
- assert.equal( val.length, 4, "insignificant white-space returned for value" );
+ val = jQuery( "<option>te" + character + "st</option>" ).val();
+ assert.equal( val, "te st", "Whitespace is collapsed in values" );
+ } );
} );
QUnit.test( "SVG class manipulation (gh-2199)", function( assert ) {
>386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401
/* Copyright 2015 Pierre Ossman for Cendio AB
 * 
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this software; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
 * USA.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <assert.h>
#include <string.h>

#include <rfb/CConnection.h>
#include <rfb/DecodeManager.h>
#include <rfb/Decoder.h>
#include <rfb/Exception.h>
#include <rfb/Region.h>
#include <rfb/LogWriter.h>
#include <rfb/util.h>

#include <rdr/Exception.h>
#include <rdr/MemOutStream.h>

#include <os/Mutex.h>

using namespace rfb;

static LogWriter vlog("DecodeManager");

DecodeManager::DecodeManager(CConnection *conn_) :
  conn(conn_), threadException(nullptr)
{
  size_t cpuCount;

  memset(decoders, 0, sizeof(decoders));

  memset(stats, 0, sizeof(stats));

  queueMutex = new os::Mutex();
  producerCond = new os::Condition(queueMutex);
  consumerCond = new os::Condition(queueMutex);

  cpuCount = os::Thread::getSystemCPUCount();
  if (cpuCount == 0) {
    vlog.error("Unable to determine the number of CPU cores on this system");
    cpuCount = 1;
  } else {
    vlog.info("Detected %d CPU core(s)", (int)cpuCount);
    // No point creating more threads than this, they'll just end up
    // wasting CPU fighting for locks
    if (cpuCount > 4)
      cpuCount = 4;
  }

  vlog.info("Creating %d decoder thread(s)", (int)cpuCount);

  while (cpuCount--) {
    // Twice as many possible entries in the queue as there
    // are worker threads to make sure they don't stall
    freeBuffers.push_back(new rdr::MemOutStream());
    freeBuffers.push_back(new rdr::MemOutStream());

    threads.push_back(new DecodeThread(this));
  }
}

DecodeManager::~DecodeManager()
{
  logStats();

  while (!threads.empty()) {
    delete threads.back();
    threads.pop_back();
  }

  delete threadException;

  while (!freeBuffers.empty()) {
    delete freeBuffers.back();
    freeBuffers.pop_back();
  }

  delete consumerCond;
  delete producerCond;
  delete queueMutex;

  for (Decoder* decoder : decoders)
    delete decoder;
}

bool DecodeManager::decodeRect(const Rect& r, int encoding,
                               ModifiablePixelBuffer* pb)
{
  Decoder *decoder;
  rdr::MemOutStream *bufferStream;
  int equiv;

  QueueEntry *entry;

  assert(pb != nullptr);

  if (!Decoder::supported(encoding)) {
    vlog.error("Unknown encoding %d", encoding);
    throw protocol_error("Unknown encoding");
  }

  if (!decoders[encoding]) {
    decoders[encoding] = Decoder::createDecoder(encoding);
    if (!decoders[encoding]) {
      vlog.error("Unknown encoding %d", encoding);
      throw protocol_error("Unknown encoding");
    }
  }

  decoder = decoders[encoding];

  // Wait for an available memory buffer
  queueMutex->lock();

  // FIXME: Should we return and let other things run here?
  while (freeBuffers.empty())
    producerCond->wait();

  // Don't pop the buffer in case we throw an exception
  // whilst reading
  bufferStream = freeBuffers.front();

  queueMutex->unlock();

  // First check if any thread has encountered a problem
  throwThreadException();

  // Read the rect
  bufferStream->clear();
  try {
    if (!decoder->readRect(r, conn->getInStream(), conn->server, bufferStream))
      return false;
  } catch (std::exception& e) {
    throw std::runtime_error(format("Error reading rect: %s", e.what()));
  }

  stats[encoding].rects++;
  stats[encoding].bytes += 12 + bufferStream->length();
  stats[encoding].pixels += r.area();
  equiv = 12 + r.area() * (conn->server.pf().bpp/8);
  stats[encoding].equivalent += equiv;

  // Then try to put it on the queue
  entry = new QueueEntry;

  entry->active = false;
  entry->rect = r;
  entry->encoding = encoding;
  entry->decoder = decoder;
  entry->server = &conn->server;
  entry->pb = pb;
  entry->bufferStream = bufferStream;

  decoder->getAffectedRegion(r, bufferStream->data(),
                             bufferStream->length(), conn->server,
                             &entry->affectedRegion);

  queueMutex->lock();

  // The workers add buffers to the end so it's safe to assume
  // the front is still the same buffer
  freeBuffers.pop_front();

  workQueue.push_back(entry);

  // We only put a single entry on the queue so waking a single
  // thread is sufficient
  consumerCond->signal();

  queueMutex->unlock();

  return true;
}

void DecodeManager::flush()
{
  queueMutex->lock();

  while (!workQueue.empty())
    producerCond->wait();

  queueMutex->unlock();

  throwThreadException();
}

void DecodeManager::logStats()
{
  size_t i;

  unsigned rects;
  unsigned long long pixels, bytes, equivalent;

  double ratio;

  rects = 0;
  pixels = bytes = equivalent = 0;

  for (i = 0;i < (sizeof(stats)/sizeof(stats[0]));i++) {
    // Did this class do anything at all?
    if (stats[i].rects == 0)
      continue;

    rects += stats[i].rects;
    pixels += stats[i].pixels;
    bytes += stats[i].bytes;
    equivalent += stats[i].equivalent;

    ratio = (double)stats[i].equivalent / stats[i].bytes;

    vlog.info("    %s: %s, %s", encodingName(i),
              siPrefix(stats[i].rects, "rects").c_str(),
              siPrefix(stats[i].pixels, "pixels").c_str());
    vlog.info("    %*s  %s (1:%g ratio)",
              (int)strlen(encodingName(i)), "",
              iecPrefix(stats[i].bytes, "B").c_str(), ratio);
  }

  ratio = (double)equivalent / bytes;

  vlog.info("  Total: %s, %s",
            siPrefix(rects, "rects").c_str(),
            siPrefix(pixels, "pixels").c_str());
  vlog.info("         %s (1:%g ratio)",
            iecPrefix(bytes, "B").c_str(), ratio);
}

void DecodeManager::setThreadException(const std::exception& e)
{
  os::AutoMutex a(queueMutex);

  if (threadException != nullptr)
    return;

  threadException = new std::runtime_error(format("Exception on worker thread: %s", e.what()));
}

void DecodeManager::throwThreadException()
{
  os::AutoMutex a(queueMutex);

  if (threadException == nullptr)
    return;

  std::exception e(*threadException);

  delete threadException;
  threadException = nullptr;

  throw e;
}

DecodeManager::DecodeThread::DecodeThread(DecodeManager* manager_)
  : manager(manager_), stopRequested(false)
{
  start();
}

DecodeManager::DecodeThread::~DecodeThread()
{
  stop();
  wait();
}

void DecodeManager::DecodeThread::stop()
{
  os::AutoMutex a(manager->queueMutex);

  if (!isRunning())
    return;

  stopRequested = true;

  // We can't wake just this thread, so wake everyone
  manager->consumerCond->broadcast();
}

void DecodeManager::DecodeThread::worker()
{
  manager->queueMutex->lock();

  while (!stopRequested) {
    DecodeManager::QueueEntry *entry;

    // Look for an available entry in the work queue
    entry = findEntry();
    if (entry == nullptr) {
      // Wait and try again
      manager->consumerCond->wait();
      continue;
    }

    // This is ours now
    entry->active = true;

    manager->queueMutex->unlock();

    // Do the actual decoding
    try {
      entry->decoder->decodeRect(entry->rect, entry->bufferStream->data(),
                                 entry->bufferStream->length(),
                                 *entry->server, entry->pb);
    } catch (std::exception& e) {
      manager->setThreadException(e);
    } catch(...) {
      assert(false);
    }

    manager->queueMutex->lock();

    // Remove the entry from the queue and give back the memory buffer
    manager->freeBuffers.push_back(entry->bufferStream);
    manager->workQueue.remove(entry);
    delete entry;

    // Wake the main thread in case it is waiting for a memory buffer
    manager->producerCond->signal();
    // This rect might have been blocking multiple other rects, so
    // wake up every worker thread
    if (manager->workQueue.size() > 1)
      manager->consumerCond->broadcast();
  }

  manager->queueMutex->unlock();
}

DecodeManager::QueueEntry* DecodeManager::DecodeThread::findEntry()
{
  Region lockedRegion;

  if (manager->workQueue.empty())
    return nullptr;

  if (!manager->workQueue.front()->active)
    return manager->workQueue.front();

  for (DecodeManager::QueueEntry* entry : manager->workQueue) {
    // Another thread working on this?
    if (entry->active)
      goto next;

    // If this is an ordered decoder then make sure this is the first
    // rectangle in the queue for that decoder
    if (entry->decoder->flags & DecoderOrdered) {
      for (DecodeManager::QueueEntry* entry2 : manager->workQueue) {
        if (entry2 == entry)
          break;
        if (entry->encoding == entry2->encoding)
          goto next;
      }
    }

    // For a partially ordered decoder we must ask the decoder for each
    // pair of rectangles.
    if (entry->decoder->flags & DecoderPartiallyOrdered) {
      for (DecodeManager::QueueEntry* entry2 : manager->workQueue) {
        if (entry2 == entry)
          break;
        if (entry->encoding != entry2->encoding)
          continue;
        if (entry->decoder->doRectsConflict(entry->rect,
                                            entry->bufferStream->data(),
                                            entry->bufferStream->length(),
                                            entry2->rect,
                                            entry2->bufferStream->data(),
                                            entry2->bufferStream->length(),
                                            *entry->server))
          goto next;
      }
    }

    // Check overlap with earlier rectangles
    if (!lockedRegion.intersect(entry->affectedRegion).is_empty())
      goto next;

    return entry;

next:
    lockedRegion.assign_union(entry->affectedRegion);
  }

  return nullptr;
}