[Pkg-mozext-commits] [wot] 57/226: Alpha version of FBL for Firefox / incomplete
David Prévot
taffit at moszumanska.debian.org
Fri May 1 00:35:34 UTC 2015
This is an automated email from the git hooks/post-receive script.
taffit pushed a commit to branch master
in repository wot.
commit 61e03254fefe4ed7c9855c592a05f6f8ba37d565
Author: Sergey Andryukhin <sorgoz at yandex.com>
Date: Fri Dec 21 14:18:16 2012 +0200
Alpha version of FBL for Firefox / incomplete
---
chrome.manifest | 4 +-
content/api.js | 70 +
content/cache.js | 52 +
content/config.js | 12 +-
content/injections/ga_configure.js | 119 +
content/injections/ga_init.js | 37 +
content/injections/jquery-ui-1.9.2.custom.js | 2544 ++++++++++++++++++++++
content/injections/jquery.js | 4 +
content/injections/surveys.widgets.js | 385 ++++
content/injections/wot_proxy.js | 356 +++
content/overlay.xul | 1 +
content/search.js | 6 +-
content/surveys.js | 548 +++++
content/ui.js | 6 +-
content/util.js | 193 ++
extra/feedback/assets/close-button-secondary.png | Bin 0 -> 3511 bytes
extra/feedback/assets/close-button.png | Bin 0 -> 3533 bytes
extra/feedback/assets/logo.png | Bin 0 -> 1706 bytes
extra/feedback/assets/slide-sep.png | Bin 0 -> 2949 bytes
extra/feedback/assets/slider-handle.png | Bin 0 -> 4005 bytes
extra/feedback/assets/submit-button.png | Bin 0 -> 11440 bytes
extra/feedback/surveys.html | 69 +
extra/feedback/surveys.widgets.css | 390 ++++
extra/feedback/surveys_test.html | 9 +
extra/feedback/surveys_test.js | 34 +
25 files changed, 4831 insertions(+), 8 deletions(-)
diff --git a/chrome.manifest b/chrome.manifest
index 4181614..6e2f945 100644
--- a/chrome.manifest
+++ b/chrome.manifest
@@ -1,5 +1,5 @@
-content wot content/
-content wot content/ contentaccessible=yes
+content wot content/
+content wot content/ contentaccessible=yes
resource wot-base-dir content/
overlay chrome://browser/content/browser.xul chrome://wot/content/overlay.xul
overlay chrome://navigator/content/navigatorOverlay.xul chrome://wot/content/overlay.xul
diff --git a/content/api.js b/content/api.js
index b9eb339..f846582 100644
--- a/content/api.js
+++ b/content/api.js
@@ -230,6 +230,8 @@ var wot_api_query =
wot_cache.set(hostname, "time", Date.now());
if (request.status == 200) {
+
+ // here is the point to process /query response and extract a FBL question
wot_cache.add_query(
request.responseXML.getElementsByTagName(
WOT_SERVICE_XML_QUERY),
@@ -800,6 +802,74 @@ var wot_api_submit =
}
};
+var wot_api_feedback =
+{
+ send: function(url, question, choice)
+ {
+ try {
+ if (!wot_util.isenabled() || !url || !choice || !question) {
+ dump("wot_api_feedback.send() - invalid params were given\n");
+ return;
+ }
+
+ var nonce = wot_crypto.nonce();
+
+ var context = wot_arc4.create(wot_hash.hmac_sha1hex(
+ wot_prefs.witness_key, nonce));
+
+ if (!context) {
+ dump("wot_api_feedback.send() - no context was given\n");
+ return;
+ }
+
+ var crypted = wot_arc4.crypt(context,
+ wot_hash.strtobin(url));
+
+ if (!crypted) {
+ dump("wot_api_feedback.send() - url encryption failed\n");
+ return;
+ }
+
+ var qs = WOT_SERVICE_API_FEEDBACK +
+ "?question=" + String(question) +
+ "&choice=" + String(choice) +
+ "&url=" + encodeURIComponent(btoa(wot_hash.bintostr(crypted))) +
+ "&id=" + wot_prefs.witness_id +
+ "&nonce=" + nonce;
+
+ qs += wot_url.getapiparams();
+
+ var request = new XMLHttpRequest();
+
+ if (!request) {
+ dump("wot_api_feedback.send() - failed to create Request object\n");
+ return;
+ }
+
+ request.open("GET", wot_core.wot_service_url() + wot_crypto.authenticate_query(qs));
+
+ new wot_cookie_remover(request);
+
+ request.onload = function(event)
+ {
+ try {
+ if (request.status == 200) {
+ dump("wot_api_feedback.onload: answer submitted successfully\n");
+ }
+ } catch (e) {
+ dump("wot_api_feedback.onload: failed with " + e + "\n");
+ }
+ };
+
+ request.send(null);
+ dump("wot_api_feedback.send() feedback was sent\n");
+
+ } catch (e) {
+ dump("wot_api_feedback.send: failed with " + e + "\n");
+ }
+ }
+};
+
var wot_api_update =
{
send: function(force)
diff --git a/content/cache.js b/content/cache.js
index 9bd9012..8168624 100644
--- a/content/cache.js
+++ b/content/cache.js
@@ -379,6 +379,9 @@ var wot_cache =
}
}
+ // process Feedback Question
+ this.add_question(name, child);
+
child = child.nextSibling;
}
} catch (e) {
@@ -386,6 +389,55 @@ var wot_cache =
}
},
+ add_question: function (hostname, target_node)
+ {
+ if (target_node.localName == WOT_SERVICE_XML_QUERY_QUESTION) {
+
+ try {
+ var doc = target_node.ownerDocument;
+ var id_node = doc.getElementsByTagName(WOT_SERVICE_XML_QUERY_QUESTION_ID).item(0),
+ text_node = doc.getElementsByTagName(WOT_SERVICE_XML_QUERY_QUESTION_TEXT).item(0),
+ choices_nodes = doc.getElementsByTagName(WOT_SERVICE_XML_QUERY_CHOICE_TEXT);
+
+ if (id_node && id_node.firstChild && text_node && text_node.firstChild) {
+ var id = String(id_node.firstChild.nodeValue),
+ text = String(text_node.firstChild.nodeValue);
+
+ if (id && text) {
+
+ var choice = choices_nodes.item(0),
+ choices = [];
+
+ while(choice) {
+
+ var choice_text = choice.firstChild.nodeValue;
+ var choice_value = choice.attributes.getNamedItem("value").value;
+
+ if (choice_text && choice_value) {
+ choices.push({ value: choice_value, text: choice_text });
+ }
+
+ choice = choice.nextSibling;
+ }
+
+ // now store question data to global WOT cache (if there are any choices)
+ if (choices.length) {
+ this.set(hostname, "question_id", id);
+ this.set(hostname, "question_text", text);
+ this.set(hostname, "choices_number", Number(choices.length));
+ for(var j=0; j < choices.length; j++) {
+ this.set(hostname, "choice_value_" + String(j), choices[j]['value']);
+ this.set(hostname, "choice_text_" + String(j), choices[j]['text']);
+ }
+ }
+ }
+ }
+ } catch(e) {
+ dump("Failed to extract Question data from XML\n");
+ }
+ }
+ },
+
add_query: function(queries, targets, islink)
{
try {
diff --git a/content/config.js b/content/config.js
index 1889594..cc4c137 100644
--- a/content/config.js
+++ b/content/config.js
@@ -70,6 +70,7 @@ const WOT_SERVICE_API_REGISTER = WOT_SERVICE_API_VERSION + "register";
const WOT_SERVICE_API_RELOAD = WOT_SERVICE_API_VERSION + "reload";
const WOT_SERVICE_API_SUBMIT = WOT_SERVICE_API_VERSION + "submit";
const WOT_SERVICE_API_UPDATE = WOT_SERVICE_API_VERSION + "update";
+const WOT_SERVICE_API_FEEDBACK = WOT_SERVICE_API_VERSION + "feedback";
/* API XML tags and attributes */
const WOT_SERVICE_XML_LINK = "link";
@@ -85,6 +86,10 @@ const WOT_SERVICE_XML_QUERY_APPLICATION_I = "inherited";
const WOT_SERVICE_XML_QUERY_APPLICATION_L = "lowered";
const WOT_SERVICE_XML_QUERY_APPLICATION_E = "excluded";
const WOT_SERVICE_XML_QUERY_APPLICATION_T = "t";
+const WOT_SERVICE_XML_QUERY_QUESTION = "question";
+const WOT_SERVICE_XML_QUERY_QUESTION_ID = "questionId";
+const WOT_SERVICE_XML_QUERY_QUESTION_TEXT = "questionText";
+const WOT_SERVICE_XML_QUERY_CHOICE_TEXT = "choiceText";
const WOT_SERVICE_XML_QUERY_MSG = "message";
const WOT_SERVICE_XML_QUERY_MSG_ID = "id";
const WOT_SERVICE_XML_QUERY_MSG_ID_MAINT = "downtime";
@@ -229,7 +234,9 @@ const wot_prefs_bool = [
[ "warning_unknown_2", false ],
[ "warning_unknown_3", false ],
[ "warning_unknown_4", false ],
- [ "warning_unknown_5", false ]
+ [ "warning_unknown_5", false ],
+ [ "feedback_enabled", true ],
+ [ "feedback_optedout", false ]
];
const wot_prefs_char = [
@@ -244,7 +251,8 @@ const wot_prefs_char = [
[ "update_checked", "0" ],
[ "warning_opacity", "" ],
[ "witness_id", "" ],
- [ "witness_key", "" ]
+ [ "witness_key", "" ],
+ [ "feedback_lasttimeasked", "" ]
];
const wot_prefs_int = [
diff --git a/content/injections/ga_configure.js b/content/injections/ga_configure.js
new file mode 100644
index 0000000..dbb66fb
--- /dev/null
+++ b/content/injections/ga_configure.js
@@ -0,0 +1,119 @@
+/*
+ wot.js
+ Copyright © 2009 - 2012 WOT Services Oy <info at mywot.com>
+
+ This file is part of WOT.
+
+ WOT 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 3 of the License, or
+ (at your option) any later version.
+
+ WOT 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 WOT. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+// this script must be placed right after wot.js
+// it is separated from html to conform strict content policy
+
+var _gaq = _gaq || [];
+_gaq.push(['_setAccount', wot.ga_id]);
+_gaq.push(['_trackPageview']);
+
+// provide version number to GA
+_gaq.push(['_setCustomVar', 1, 'Version', String(wot.version), 2]); // scope = 2 (session level)
+
+
+/* This adds logic for counting events to wot object */
+
+$.extend(wot, { ga: {
+
+ disable: false, // general switcher to stop counting stats
+ _tracker: null,
+
+ categories: {
+ WS: "WarningScreen",
+ RW: "RatingWindow",
+ GEN: "General",
+ INJ: "Injections",
+ WT: "WelcomeTips",
+ FBL: "FeedbackLoop"
+ },
+
+ actions: {
+ RW_TESTIMONY: "RW_testimony",
+ RW_BTN_CLOSE: "RW_btn_close",
+ RW_MSG_CLICKED: "RW_msg_clicked",
+
+ WS_SHOW: "WS_shown",
+ WS_BTN_ENTER: "WS_btn_enter",
+ WS_BTN_CLOSE: "WS_btn_close",
+
+ D_POPUP_SHOWN: "D_popup_shown",
+ GEN_INSTALLED: "WOT_installed",
+ GEN_LAUNCHED: "WOT_launched",
+
+ WT_INTRO_0_SHOWN: "WT_Intro0_shown",
+ WT_INTRO_0_OK: "WT_Intro0_ok",
+ WT_WS_SHOWN: "WT_WS_shown",
+ WT_WS_OK: "WT_WS_ok",
+ WT_WS_OPTEDOUT: "WT_WS_optedout",
+ WT_RW_SHOWN: "WT_RW_shown",
+ WT_RW_OK: "WT_RW_ok",
+ WT_DONUTS_SHOWN:"WT_Donuts_shown",
+ WT_DONUTS_OK: "WT_Donuts_ok",
+
+ FBL_shown: "FBL_shown",
+ FBL_submit: "FBL_submit",
+ FBL_closed: "FBL_closed",
+ FBL_optout_shown:"FBL_optout_shown",
+ FBL_optout_yes: "FBL_optout_yes",
+ FBL_optout_no: "FBL_optout_no",
+ FBL_whatisthis: "FBL_whatisthis",
+ FBL_bottom_close:"FBL_bottom_close",
+ FBL_slidered: "FBL_slidered",
+ FBL_directclick: "FBL_directclick",
+ FBL_logo: "FBL_logo",
+ FBL_opportunity: "FBL_opportunity" // we could show the survey, but conditions are not met
+ },
+
+ init_tracker: function () {
+ if (!wot.ga._tracker) {
+ if (_gat) {
+ try {
+ wot.ga._tracker = _gat.getTrackerByName();
+ wot.ga._tracker._setAccount(wot.ga_id);
+ } catch (e) {
+ // failed. No Problem.
+ }
+ }
+ }
+
+ return !!wot.ga._tracker;
+ },
+
+ fire_event: function (category, action, label, value) {
+
+ if (wot.ga.disable) return;
+
+ try {
+ if (wot.ga.init_tracker()) {
+ wot.ga._tracker._trackEvent(category, action, label, value);
+ } else {
+ // backup option, if AsyncTracker still isn't inited
+ _gaq.push(['_trackEvent', category, action, label, value]);
+ }
+ } catch (e) {
+ // silence...
+ //console.log("Error in wot.ga.fire_event(). Msg: ", e);
+ }
+ }
+
+}});
+
diff --git a/content/injections/ga_init.js b/content/injections/ga_init.js
new file mode 100644
index 0000000..0e4d226
--- /dev/null
+++ b/content/injections/ga_init.js
@@ -0,0 +1,37 @@
+/*
+ wot.js
+ Copyright © 2009 - 2012 WOT Services Oy <info at mywot.com>
+
+ This file is part of WOT.
+
+ WOT 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 3 of the License, or
+ (at your option) any later version.
+
+ WOT 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 WOT. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+// for analytics
+(function() {
+ if(!wot.ga.disable) {
+ try {
+ var ga = document.createElement('script');
+ ga.type = 'text/javascript';
+ ga.async = true;
+ ga.src = 'https://ssl.google-analytics.com/ga.js';
+
+ var s = document.getElementsByTagName('script')[0];
+ s.parentNode.insertBefore(ga, s);
+ } catch (e) {
+ // silence, please
+ }
+ }
+})();
diff --git a/content/injections/jquery-ui-1.9.2.custom.js b/content/injections/jquery-ui-1.9.2.custom.js
new file mode 100755
index 0000000..b4bae10
--- /dev/null
+++ b/content/injections/jquery-ui-1.9.2.custom.js
@@ -0,0 +1,2544 @@
+/*! jQuery UI - v1.9.2 - 2012-11-29
+* http://jqueryui.com
+* Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.position.js, jquery.ui.slider.js, jquery.ui.tooltip.js
+* Copyright (c) 2012 jQuery Foundation and other contributors Licensed MIT */
+
+(function( $, undefined ) {
+
+var uuid = 0,
+ runiqueId = /^ui-id-\d+$/;
+
+// prevent duplicate loading
+// this is only a problem because we proxy existing functions
+// and we don't want to double proxy them
+$.ui = $.ui || {};
+if ( $.ui.version ) {
+ return;
+}
+
+$.extend( $.ui, {
+ version: "1.9.2",
+
+ keyCode: {
+ BACKSPACE: 8,
+ COMMA: 188,
+ DELETE: 46,
+ DOWN: 40,
+ END: 35,
+ ENTER: 13,
+ ESCAPE: 27,
+ HOME: 36,
+ LEFT: 37,
+ NUMPAD_ADD: 107,
+ NUMPAD_DECIMAL: 110,
+ NUMPAD_DIVIDE: 111,
+ NUMPAD_ENTER: 108,
+ NUMPAD_MULTIPLY: 106,
+ NUMPAD_SUBTRACT: 109,
+ PAGE_DOWN: 34,
+ PAGE_UP: 33,
+ PERIOD: 190,
+ RIGHT: 39,
+ SPACE: 32,
+ TAB: 9,
+ UP: 38
+ }
+});
+
+// plugins
+$.fn.extend({
+ _focus: $.fn.focus,
+ focus: function( delay, fn ) {
+ return typeof delay === "number" ?
+ this.each(function() {
+ var elem = this;
+ setTimeout(function() {
+ $( elem ).focus();
+ if ( fn ) {
+ fn.call( elem );
+ }
+ }, delay );
+ }) :
+ this._focus.apply( this, arguments );
+ },
+
+ scrollParent: function() {
+ var scrollParent;
+ if (($.ui.ie && (/(static|relative)/).test(this.css('position'))) || (/absolute/).test(this.css('position'))) {
+ scrollParent = this.parents().filter(function() {
+ return (/(relative|absolute|fixed)/).test($.css(this,'position')) && (/(auto|scroll)/).test($.css(this,'overflow')+$.css(this,'overflow-y')+$.css(this,'overflow-x'));
+ }).eq(0);
+ } else {
+ scrollParent = this.parents().filter(function() {
+ return (/(auto|scroll)/).test($.css(this,'overflow')+$.css(this,'overflow-y')+$.css(this,'overflow-x'));
+ }).eq(0);
+ }
+
+ return (/fixed/).test(this.css('position')) || !scrollParent.length ? $(document) : scrollParent;
+ },
+
+ zIndex: function( zIndex ) {
+ if ( zIndex !== undefined ) {
+ return this.css( "zIndex", zIndex );
+ }
+
+ if ( this.length ) {
+ var elem = $( this[ 0 ] ), position, value;
+ while ( elem.length && elem[ 0 ] !== document ) {
+ // Ignore z-index if position is set to a value where z-index is ignored by the browser
+ // This makes behavior of this function consistent across browsers
+ // WebKit always returns auto if the element is positioned
+ position = elem.css( "position" );
+ if ( position === "absolute" || position === "relative" || position === "fixed" ) {
+ // IE returns 0 when zIndex is not specified
+ // other browsers return a string
+ // we ignore the case of nested elements with an explicit value of 0
+ // <div style="z-index: -10;"><div style="z-index: 0;"></div></div>
+ value = parseInt( elem.css( "zIndex" ), 10 );
+ if ( !isNaN( value ) && value !== 0 ) {
+ return value;
+ }
+ }
+ elem = elem.parent();
+ }
+ }
+
+ return 0;
+ },
+
+ uniqueId: function() {
+ return this.each(function() {
+ if ( !this.id ) {
+ this.id = "ui-id-" + (++uuid);
+ }
+ });
+ },
+
+ removeUniqueId: function() {
+ return this.each(function() {
+ if ( runiqueId.test( this.id ) ) {
+ $( this ).removeAttr( "id" );
+ }
+ });
+ }
+});
+
+// selectors
+function focusable( element, isTabIndexNotNaN ) {
+ var map, mapName, img,
+ nodeName = element.nodeName.toLowerCase();
+ if ( "area" === nodeName ) {
+ map = element.parentNode;
+ mapName = map.name;
+ if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) {
+ return false;
+ }
+ img = $( "img[usemap=#" + mapName + "]" )[0];
+ return !!img && visible( img );
+ }
+ return ( /input|select|textarea|button|object/.test( nodeName ) ?
+ !element.disabled :
+ "a" === nodeName ?
+ element.href || isTabIndexNotNaN :
+ isTabIndexNotNaN) &&
+ // the element and all of its ancestors must be visible
+ visible( element );
+}
+
+function visible( element ) {
+ return $.expr.filters.visible( element ) &&
+ !$( element ).parents().andSelf().filter(function() {
+ return $.css( this, "visibility" ) === "hidden";
+ }).length;
+}
+
+$.extend( $.expr[ ":" ], {
+ data: $.expr.createPseudo ?
+ $.expr.createPseudo(function( dataName ) {
+ return function( elem ) {
+ return !!$.data( elem, dataName );
+ };
+ }) :
+ // support: jQuery <1.8
+ function( elem, i, match ) {
+ return !!$.data( elem, match[ 3 ] );
+ },
+
+ focusable: function( element ) {
+ return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) );
+ },
+
+ tabbable: function( element ) {
+ var tabIndex = $.attr( element, "tabindex" ),
+ isTabIndexNaN = isNaN( tabIndex );
+ return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN );
+ }
+});
+
+// support
+$(function() {
+ var body = document.body,
+ div = body.appendChild( div = document.createElement( "div" ) );
+
+ // access offsetHeight before setting the style to prevent a layout bug
+ // in IE 9 which causes the element to continue to take up space even
+ // after it is removed from the DOM (#8026)
+ div.offsetHeight;
+
+ $.extend( div.style, {
+ minHeight: "100px",
+ height: "auto",
+ padding: 0,
+ borderWidth: 0
+ });
+
+ $.support.minHeight = div.offsetHeight === 100;
+ $.support.selectstart = "onselectstart" in div;
+
+ // set display to none to avoid a layout bug in IE
+ // http://dev.jquery.com/ticket/4014
+ body.removeChild( div ).style.display = "none";
+});
+
+// support: jQuery <1.8
+if ( !$( "<a>" ).outerWidth( 1 ).jquery ) {
+ $.each( [ "Width", "Height" ], function( i, name ) {
+ var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ],
+ type = name.toLowerCase(),
+ orig = {
+ innerWidth: $.fn.innerWidth,
+ innerHeight: $.fn.innerHeight,
+ outerWidth: $.fn.outerWidth,
+ outerHeight: $.fn.outerHeight
+ };
+
+ function reduce( elem, size, border, margin ) {
+ $.each( side, function() {
+ size -= parseFloat( $.css( elem, "padding" + this ) ) || 0;
+ if ( border ) {
+ size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0;
+ }
+ if ( margin ) {
+ size -= parseFloat( $.css( elem, "margin" + this ) ) || 0;
+ }
+ });
+ return size;
+ }
+
+ $.fn[ "inner" + name ] = function( size ) {
+ if ( size === undefined ) {
+ return orig[ "inner" + name ].call( this );
+ }
+
+ return this.each(function() {
+ $( this ).css( type, reduce( this, size ) + "px" );
+ });
+ };
+
+ $.fn[ "outer" + name] = function( size, margin ) {
+ if ( typeof size !== "number" ) {
+ return orig[ "outer" + name ].call( this, size );
+ }
+
+ return this.each(function() {
+ $( this).css( type, reduce( this, size, true, margin ) + "px" );
+ });
+ };
+ });
+}
+
+// support: jQuery 1.6.1, 1.6.2 (http://bugs.jquery.com/ticket/9413)
+if ( $( "<a>" ).data( "a-b", "a" ).removeData( "a-b" ).data( "a-b" ) ) {
+ $.fn.removeData = (function( removeData ) {
+ return function( key ) {
+ if ( arguments.length ) {
+ return removeData.call( this, $.camelCase( key ) );
+ } else {
+ return removeData.call( this );
+ }
+ };
+ })( $.fn.removeData );
+}
+
+
+
+
+
+// deprecated
+
+(function() {
+ var uaMatch = /msie ([\w.]+)/.exec( navigator.userAgent.toLowerCase() ) || [];
+ $.ui.ie = uaMatch.length ? true : false;
+ $.ui.ie6 = parseFloat( uaMatch[ 1 ], 10 ) === 6;
+})();
+
+$.fn.extend({
+ disableSelection: function() {
+ return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) +
+ ".ui-disableSelection", function( event ) {
+ event.preventDefault();
+ });
+ },
+
+ enableSelection: function() {
+ return this.unbind( ".ui-disableSelection" );
+ }
+});
+
+$.extend( $.ui, {
+ // $.ui.plugin is deprecated. Use the proxy pattern instead.
+ plugin: {
+ add: function( module, option, set ) {
+ var i,
+ proto = $.ui[ module ].prototype;
+ for ( i in set ) {
+ proto.plugins[ i ] = proto.plugins[ i ] || [];
+ proto.plugins[ i ].push( [ option, set[ i ] ] );
+ }
+ },
+ call: function( instance, name, args ) {
+ var i,
+ set = instance.plugins[ name ];
+ if ( !set || !instance.element[ 0 ].parentNode || instance.element[ 0 ].parentNode.nodeType === 11 ) {
+ return;
+ }
+
+ for ( i = 0; i < set.length; i++ ) {
+ if ( instance.options[ set[ i ][ 0 ] ] ) {
+ set[ i ][ 1 ].apply( instance.element, args );
+ }
+ }
+ }
+ },
+
+ contains: $.contains,
+
+ // only used by resizable
+ hasScroll: function( el, a ) {
+
+ //If overflow is hidden, the element might have extra content, but the user wants to hide it
+ if ( $( el ).css( "overflow" ) === "hidden") {
+ return false;
+ }
+
+ var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop",
+ has = false;
+
+ if ( el[ scroll ] > 0 ) {
+ return true;
+ }
+
+ // TODO: determine which cases actually cause this to happen
+ // if the element doesn't have the scroll set, see if it's possible to
+ // set the scroll
+ el[ scroll ] = 1;
+ has = ( el[ scroll ] > 0 );
+ el[ scroll ] = 0;
+ return has;
+ },
+
+ // these are odd functions, fix the API or move into individual plugins
+ isOverAxis: function( x, reference, size ) {
+ //Determines when x coordinate is over "b" element axis
+ return ( x > reference ) && ( x < ( reference + size ) );
+ },
+ isOver: function( y, x, top, left, height, width ) {
+ //Determines when x, y coordinates is over "b" element
+ return $.ui.isOverAxis( y, top, height ) && $.ui.isOverAxis( x, left, width );
+ }
+});
+
+})( jQuery );
+(function( $, undefined ) {
+
+var uuid = 0,
+ slice = Array.prototype.slice,
+ _cleanData = $.cleanData;
+$.cleanData = function( elems ) {
+ for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+ try {
+ $( elem ).triggerHandler( "remove" );
+ // http://bugs.jquery.com/ticket/8235
+ } catch( e ) {}
+ }
+ _cleanData( elems );
+};
+
+$.widget = function( name, base, prototype ) {
+ var fullName, existingConstructor, constructor, basePrototype,
+ namespace = name.split( "." )[ 0 ];
+
+ name = name.split( "." )[ 1 ];
+ fullName = namespace + "-" + name;
+
+ if ( !prototype ) {
+ prototype = base;
+ base = $.Widget;
+ }
+
+ // create selector for plugin
+ $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
+ return !!$.data( elem, fullName );
+ };
+
+ $[ namespace ] = $[ namespace ] || {};
+ existingConstructor = $[ namespace ][ name ];
+ constructor = $[ namespace ][ name ] = function( options, element ) {
+ // allow instantiation without "new" keyword
+ if ( !this._createWidget ) {
+ return new constructor( options, element );
+ }
+
+ // allow instantiation without initializing for simple inheritance
+ // must use "new" keyword (the code above always passes args)
+ if ( arguments.length ) {
+ this._createWidget( options, element );
+ }
+ };
+ // extend with the existing constructor to carry over any static properties
+ $.extend( constructor, existingConstructor, {
+ version: prototype.version,
+ // copy the object used to create the prototype in case we need to
+ // redefine the widget later
+ _proto: $.extend( {}, prototype ),
+ // track widgets that inherit from this widget in case this widget is
+ // redefined after a widget inherits from it
+ _childConstructors: []
+ });
+
+ basePrototype = new base();
+ // we need to make the options hash a property directly on the new instance
+ // otherwise we'll modify the options hash on the prototype that we're
+ // inheriting from
+ basePrototype.options = $.widget.extend( {}, basePrototype.options );
+ $.each( prototype, function( prop, value ) {
+ if ( $.isFunction( value ) ) {
+ prototype[ prop ] = (function() {
+ var _super = function() {
+ return base.prototype[ prop ].apply( this, arguments );
+ },
+ _superApply = function( args ) {
+ return base.prototype[ prop ].apply( this, args );
+ };
+ return function() {
+ var __super = this._super,
+ __superApply = this._superApply,
+ returnValue;
+
+ this._super = _super;
+ this._superApply = _superApply;
+
+ returnValue = value.apply( this, arguments );
+
+ this._super = __super;
+ this._superApply = __superApply;
+
+ return returnValue;
+ };
+ })();
+ }
+ });
+ constructor.prototype = $.widget.extend( basePrototype, {
+ // TODO: remove support for widgetEventPrefix
+ // always use the name + a colon as the prefix, e.g., draggable:start
+ // don't prefix for widgets that aren't DOM-based
+ widgetEventPrefix: existingConstructor ? basePrototype.widgetEventPrefix : name
+ }, prototype, {
+ constructor: constructor,
+ namespace: namespace,
+ widgetName: name,
+ // TODO remove widgetBaseClass, see #8155
+ widgetBaseClass: fullName,
+ widgetFullName: fullName
+ });
+
+ // If this widget is being redefined then we need to find all widgets that
+ // are inheriting from it and redefine all of them so that they inherit from
+ // the new version of this widget. We're essentially trying to replace one
+ // level in the prototype chain.
+ if ( existingConstructor ) {
+ $.each( existingConstructor._childConstructors, function( i, child ) {
+ var childPrototype = child.prototype;
+
+ // redefine the child widget using the same prototype that was
+ // originally used, but inherit from the new version of the base
+ $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );
+ });
+ // remove the list of existing child constructors from the old constructor
+ // so the old child constructors can be garbage collected
+ delete existingConstructor._childConstructors;
+ } else {
+ base._childConstructors.push( constructor );
+ }
+
+ $.widget.bridge( name, constructor );
+};
+
+$.widget.extend = function( target ) {
+ var input = slice.call( arguments, 1 ),
+ inputIndex = 0,
+ inputLength = input.length,
+ key,
+ value;
+ for ( ; inputIndex < inputLength; inputIndex++ ) {
+ for ( key in input[ inputIndex ] ) {
+ value = input[ inputIndex ][ key ];
+ if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
+ // Clone objects
+ if ( $.isPlainObject( value ) ) {
+ target[ key ] = $.isPlainObject( target[ key ] ) ?
+ $.widget.extend( {}, target[ key ], value ) :
+ // Don't extend strings, arrays, etc. with objects
+ $.widget.extend( {}, value );
+ // Copy everything else by reference
+ } else {
+ target[ key ] = value;
+ }
+ }
+ }
+ }
+ return target;
+};
+
+$.widget.bridge = function( name, object ) {
+ var fullName = object.prototype.widgetFullName || name;
+ $.fn[ name ] = function( options ) {
+ var isMethodCall = typeof options === "string",
+ args = slice.call( arguments, 1 ),
+ returnValue = this;
+
+ // allow multiple hashes to be passed on init
+ options = !isMethodCall && args.length ?
+ $.widget.extend.apply( null, [ options ].concat(args) ) :
+ options;
+
+ if ( isMethodCall ) {
+ this.each(function() {
+ var methodValue,
+ instance = $.data( this, fullName );
+ if ( !instance ) {
+ return $.error( "cannot call methods on " + name + " prior to initialization; " +
+ "attempted to call method '" + options + "'" );
+ }
+ if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
+ return $.error( "no such method '" + options + "' for " + name + " widget instance" );
+ }
+ methodValue = instance[ options ].apply( instance, args );
+ if ( methodValue !== instance && methodValue !== undefined ) {
+ returnValue = methodValue && methodValue.jquery ?
+ returnValue.pushStack( methodValue.get() ) :
+ methodValue;
+ return false;
+ }
+ });
+ } else {
+ this.each(function() {
+ var instance = $.data( this, fullName );
+ if ( instance ) {
+ instance.option( options || {} )._init();
+ } else {
+ $.data( this, fullName, new object( options, this ) );
+ }
+ });
+ }
+
+ return returnValue;
+ };
+};
+
+$.Widget = function( /* options, element */ ) {};
+$.Widget._childConstructors = [];
+
+$.Widget.prototype = {
+ widgetName: "widget",
+ widgetEventPrefix: "",
+ defaultElement: "<div>",
+ options: {
+ disabled: false,
+
+ // callbacks
+ create: null
+ },
+ _createWidget: function( options, element ) {
+ element = $( element || this.defaultElement || this )[ 0 ];
+ this.element = $( element );
+ this.uuid = uuid++;
+ this.eventNamespace = "." + this.widgetName + this.uuid;
+ this.options = $.widget.extend( {},
+ this.options,
+ this._getCreateOptions(),
+ options );
+
+ this.bindings = $();
+ this.hoverable = $();
+ this.focusable = $();
+
+ if ( element !== this ) {
+ // 1.9 BC for #7810
+ // TODO remove dual storage
+ $.data( element, this.widgetName, this );
+ $.data( element, this.widgetFullName, this );
+ this._on( true, this.element, {
+ remove: function( event ) {
+ if ( event.target === element ) {
+ this.destroy();
+ }
+ }
+ });
+ this.document = $( element.style ?
+ // element within the document
+ element.ownerDocument :
+ // element is window or document
+ element.document || element );
+ this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
+ }
+
+ this._create();
+ this._trigger( "create", null, this._getCreateEventData() );
+ this._init();
+ },
+ _getCreateOptions: $.noop,
+ _getCreateEventData: $.noop,
+ _create: $.noop,
+ _init: $.noop,
+
+ destroy: function() {
+ this._destroy();
+ // we can probably remove the unbind calls in 2.0
+ // all event bindings should go through this._on()
+ this.element
+ .unbind( this.eventNamespace )
+ // 1.9 BC for #7810
+ // TODO remove dual storage
+ .removeData( this.widgetName )
+ .removeData( this.widgetFullName )
+ // support: jquery <1.6.3
+ // http://bugs.jquery.com/ticket/9413
+ .removeData( $.camelCase( this.widgetFullName ) );
+ this.widget()
+ .unbind( this.eventNamespace )
+ .removeAttr( "aria-disabled" )
+ .removeClass(
+ this.widgetFullName + "-disabled " +
+ "ui-state-disabled" );
+
+ // clean up events and states
+ this.bindings.unbind( this.eventNamespace );
+ this.hoverable.removeClass( "ui-state-hover" );
+ this.focusable.removeClass( "ui-state-focus" );
+ },
+ _destroy: $.noop,
+
+ widget: function() {
+ return this.element;
+ },
+
+ option: function( key, value ) {
+ var options = key,
+ parts,
+ curOption,
+ i;
+
+ if ( arguments.length === 0 ) {
+ // don't return a reference to the internal hash
+ return $.widget.extend( {}, this.options );
+ }
+
+ if ( typeof key === "string" ) {
+ // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
+ options = {};
+ parts = key.split( "." );
+ key = parts.shift();
+ if ( parts.length ) {
+ curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
+ for ( i = 0; i < parts.length - 1; i++ ) {
+ curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
+ curOption = curOption[ parts[ i ] ];
+ }
+ key = parts.pop();
+ if ( value === undefined ) {
+ return curOption[ key ] === undefined ? null : curOption[ key ];
+ }
+ curOption[ key ] = value;
+ } else {
+ if ( value === undefined ) {
+ return this.options[ key ] === undefined ? null : this.options[ key ];
+ }
+ options[ key ] = value;
+ }
+ }
+
+ this._setOptions( options );
+
+ return this;
+ },
+ _setOptions: function( options ) {
+ var key;
+
+ for ( key in options ) {
+ this._setOption( key, options[ key ] );
+ }
+
+ return this;
+ },
+ _setOption: function( key, value ) {
+ this.options[ key ] = value;
+
+ if ( key === "disabled" ) {
+ this.widget()
+ .toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value )
+ .attr( "aria-disabled", value );
+ this.hoverable.removeClass( "ui-state-hover" );
+ this.focusable.removeClass( "ui-state-focus" );
+ }
+
+ return this;
+ },
+
+ enable: function() {
+ return this._setOption( "disabled", false );
+ },
+ disable: function() {
+ return this._setOption( "disabled", true );
+ },
+
+ _on: function( suppressDisabledCheck, element, handlers ) {
+ var delegateElement,
+ instance = this;
+
+ // no suppressDisabledCheck flag, shuffle arguments
+ if ( typeof suppressDisabledCheck !== "boolean" ) {
+ handlers = element;
+ element = suppressDisabledCheck;
+ suppressDisabledCheck = false;
+ }
+
+ // no element argument, shuffle and use this.element
+ if ( !handlers ) {
+ handlers = element;
+ element = this.element;
+ delegateElement = this.widget();
+ } else {
+ // accept selectors, DOM elements
+ element = delegateElement = $( element );
+ this.bindings = this.bindings.add( element );
+ }
+
+ $.each( handlers, function( event, handler ) {
+ function handlerProxy() {
+ // allow widgets to customize the disabled handling
+ // - disabled as an array instead of boolean
+ // - disabled class as method for disabling individual parts
+ if ( !suppressDisabledCheck &&
+ ( instance.options.disabled === true ||
+ $( this ).hasClass( "ui-state-disabled" ) ) ) {
+ return;
+ }
+ return ( typeof handler === "string" ? instance[ handler ] : handler )
+ .apply( instance, arguments );
+ }
+
+ // copy the guid so direct unbinding works
+ if ( typeof handler !== "string" ) {
+ handlerProxy.guid = handler.guid =
+ handler.guid || handlerProxy.guid || $.guid++;
+ }
+
+ var match = event.match( /^(\w+)\s*(.*)$/ ),
+ eventName = match[1] + instance.eventNamespace,
+ selector = match[2];
+ if ( selector ) {
+ delegateElement.delegate( selector, eventName, handlerProxy );
+ } else {
+ element.bind( eventName, handlerProxy );
+ }
+ });
+ },
+
+ _off: function( element, eventName ) {
+ eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace;
+ element.unbind( eventName ).undelegate( eventName );
+ },
+
+ _delay: function( handler, delay ) {
+ function handlerProxy() {
+ return ( typeof handler === "string" ? instance[ handler ] : handler )
+ .apply( instance, arguments );
+ }
+ var instance = this;
+ return setTimeout( handlerProxy, delay || 0 );
+ },
+
+ _hoverable: function( element ) {
+ this.hoverable = this.hoverable.add( element );
+ this._on( element, {
+ mouseenter: function( event ) {
+ $( event.currentTarget ).addClass( "ui-state-hover" );
+ },
+ mouseleave: function( event ) {
+ $( event.currentTarget ).removeClass( "ui-state-hover" );
+ }
+ });
+ },
+
+ _focusable: function( element ) {
+ this.focusable = this.focusable.add( element );
+ this._on( element, {
+ focusin: function( event ) {
+ $( event.currentTarget ).addClass( "ui-state-focus" );
+ },
+ focusout: function( event ) {
+ $( event.currentTarget ).removeClass( "ui-state-focus" );
+ }
+ });
+ },
+
+ _trigger: function( type, event, data ) {
+ var prop, orig,
+ callback = this.options[ type ];
+
+ data = data || {};
+ event = $.Event( event );
+ event.type = ( type === this.widgetEventPrefix ?
+ type :
+ this.widgetEventPrefix + type ).toLowerCase();
+ // the original event may come from any element
+ // so we need to reset the target on the new event
+ event.target = this.element[ 0 ];
+
+ // copy original event properties over to the new event
+ orig = event.originalEvent;
+ if ( orig ) {
+ for ( prop in orig ) {
+ if ( !( prop in event ) ) {
+ event[ prop ] = orig[ prop ];
+ }
+ }
+ }
+
+ this.element.trigger( event, data );
+ return !( $.isFunction( callback ) &&
+ callback.apply( this.element[0], [ event ].concat( data ) ) === false ||
+ event.isDefaultPrevented() );
+ }
+};
+
+$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
+ $.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
+ if ( typeof options === "string" ) {
+ options = { effect: options };
+ }
+ var hasOptions,
+ effectName = !options ?
+ method :
+ options === true || typeof options === "number" ?
+ defaultEffect :
+ options.effect || defaultEffect;
+ options = options || {};
+ if ( typeof options === "number" ) {
+ options = { duration: options };
+ }
+ hasOptions = !$.isEmptyObject( options );
+ options.complete = callback;
+ if ( options.delay ) {
+ element.delay( options.delay );
+ }
+ if ( hasOptions && $.effects && ( $.effects.effect[ effectName ] || $.uiBackCompat !== false && $.effects[ effectName ] ) ) {
+ element[ method ]( options );
+ } else if ( effectName !== method && element[ effectName ] ) {
+ element[ effectName ]( options.duration, options.easing, callback );
+ } else {
+ element.queue(function( next ) {
+ $( this )[ method ]();
+ if ( callback ) {
+ callback.call( element[ 0 ] );
+ }
+ next();
+ });
+ }
+ };
+});
+
+// DEPRECATED
+if ( $.uiBackCompat !== false ) {
+ $.Widget.prototype._getCreateOptions = function() {
+ return $.metadata && $.metadata.get( this.element[0] )[ this.widgetName ];
+ };
+}
+
+})( jQuery );
+(function( $, undefined ) {
+
+var mouseHandled = false;
+$( document ).mouseup( function( e ) {
+ mouseHandled = false;
+});
+
+$.widget("ui.mouse", {
+ version: "1.9.2",
+ options: {
+ cancel: 'input,textarea,button,select,option',
+ distance: 1,
+ delay: 0
+ },
+ _mouseInit: function() {
+ var that = this;
+
+ this.element
+ .bind('mousedown.'+this.widgetName, function(event) {
+ return that._mouseDown(event);
+ })
+ .bind('click.'+this.widgetName, function(event) {
+ if (true === $.data(event.target, that.widgetName + '.preventClickEvent')) {
+ $.removeData(event.target, that.widgetName + '.preventClickEvent');
+ event.stopImmediatePropagation();
+ return false;
+ }
+ });
+
+ this.started = false;
+ },
+
+ // TODO: make sure destroying one instance of mouse doesn't mess with
+ // other instances of mouse
+ _mouseDestroy: function() {
+ this.element.unbind('.'+this.widgetName);
+ if ( this._mouseMoveDelegate ) {
+ $(document)
+ .unbind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
+ .unbind('mouseup.'+this.widgetName, this._mouseUpDelegate);
+ }
+ },
+
+ _mouseDown: function(event) {
+ // don't let more than one widget handle mouseStart
+ if( mouseHandled ) { return; }
+
+ // we may have missed mouseup (out of window)
+ (this._mouseStarted && this._mouseUp(event));
+
+ this._mouseDownEvent = event;
+
+ var that = this,
+ btnIsLeft = (event.which === 1),
+ // event.target.nodeName works around a bug in IE 8 with
+ // disabled inputs (#7620)
+ elIsCancel = (typeof this.options.cancel === "string" && event.target.nodeName ? $(event.target).closest(this.options.cancel).length : false);
+ if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) {
+ return true;
+ }
+
+ this.mouseDelayMet = !this.options.delay;
+ if (!this.mouseDelayMet) {
+ this._mouseDelayTimer = setTimeout(function() {
+ that.mouseDelayMet = true;
+ }, this.options.delay);
+ }
+
+ if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
+ this._mouseStarted = (this._mouseStart(event) !== false);
+ if (!this._mouseStarted) {
+ event.preventDefault();
+ return true;
+ }
+ }
+
+ // Click event may never have fired (Gecko & Opera)
+ if (true === $.data(event.target, this.widgetName + '.preventClickEvent')) {
+ $.removeData(event.target, this.widgetName + '.preventClickEvent');
+ }
+
+ // these delegates are required to keep context
+ this._mouseMoveDelegate = function(event) {
+ return that._mouseMove(event);
+ };
+ this._mouseUpDelegate = function(event) {
+ return that._mouseUp(event);
+ };
+ $(document)
+ .bind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
+ .bind('mouseup.'+this.widgetName, this._mouseUpDelegate);
+
+ event.preventDefault();
+
+ mouseHandled = true;
+ return true;
+ },
+
+ _mouseMove: function(event) {
+ // IE mouseup check - mouseup happened when mouse was out of window
+ if ($.ui.ie && !(document.documentMode >= 9) && !event.button) {
+ return this._mouseUp(event);
+ }
+
+ if (this._mouseStarted) {
+ this._mouseDrag(event);
+ return event.preventDefault();
+ }
+
+ if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
+ this._mouseStarted =
+ (this._mouseStart(this._mouseDownEvent, event) !== false);
+ (this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event));
+ }
+
+ return !this._mouseStarted;
+ },
+
+ _mouseUp: function(event) {
+ $(document)
+ .unbind('mousemove.'+this.widgetName, this._mouseMoveDelegate)
+ .unbind('mouseup.'+this.widgetName, this._mouseUpDelegate);
+
+ if (this._mouseStarted) {
+ this._mouseStarted = false;
+
+ if (event.target === this._mouseDownEvent.target) {
+ $.data(event.target, this.widgetName + '.preventClickEvent', true);
+ }
+
+ this._mouseStop(event);
+ }
+
+ return false;
+ },
+
+ _mouseDistanceMet: function(event) {
+ return (Math.max(
+ Math.abs(this._mouseDownEvent.pageX - event.pageX),
+ Math.abs(this._mouseDownEvent.pageY - event.pageY)
+ ) >= this.options.distance
+ );
+ },
+
+ _mouseDelayMet: function(event) {
+ return this.mouseDelayMet;
+ },
+
+ // These are placeholder methods, to be overriden by extending plugin
+ _mouseStart: function(event) {},
+ _mouseDrag: function(event) {},
+ _mouseStop: function(event) {},
+ _mouseCapture: function(event) { return true; }
+});
+
+})(jQuery);
+(function( $, undefined ) {
+
+$.ui = $.ui || {};
+
+var cachedScrollbarWidth,
+ max = Math.max,
+ abs = Math.abs,
+ round = Math.round,
+ rhorizontal = /left|center|right/,
+ rvertical = /top|center|bottom/,
+ roffset = /[\+\-]\d+%?/,
+ rposition = /^\w+/,
+ rpercent = /%$/,
+ _position = $.fn.position;
+
+function getOffsets( offsets, width, height ) {
+ return [
+ parseInt( offsets[ 0 ], 10 ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ),
+ parseInt( offsets[ 1 ], 10 ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 )
+ ];
+}
+function parseCss( element, property ) {
+ return parseInt( $.css( element, property ), 10 ) || 0;
+}
+
+$.position = {
+ scrollbarWidth: function() {
+ if ( cachedScrollbarWidth !== undefined ) {
+ return cachedScrollbarWidth;
+ }
+ var w1, w2,
+ div = $( "<div style='display:block;width:50px;height:50px;overflow:hidden;'><div style='height:100px;width:auto;'></div></div>" ),
+ innerDiv = div.children()[0];
+
+ $( "body" ).append( div );
+ w1 = innerDiv.offsetWidth;
+ div.css( "overflow", "scroll" );
+
+ w2 = innerDiv.offsetWidth;
+
+ if ( w1 === w2 ) {
+ w2 = div[0].clientWidth;
+ }
+
+ div.remove();
+
+ return (cachedScrollbarWidth = w1 - w2);
+ },
+ getScrollInfo: function( within ) {
+ var overflowX = within.isWindow ? "" : within.element.css( "overflow-x" ),
+ overflowY = within.isWindow ? "" : within.element.css( "overflow-y" ),
+ hasOverflowX = overflowX === "scroll" ||
+ ( overflowX === "auto" && within.width < within.element[0].scrollWidth ),
+ hasOverflowY = overflowY === "scroll" ||
+ ( overflowY === "auto" && within.height < within.element[0].scrollHeight );
+ return {
+ width: hasOverflowX ? $.position.scrollbarWidth() : 0,
+ height: hasOverflowY ? $.position.scrollbarWidth() : 0
+ };
+ },
+ getWithinInfo: function( element ) {
+ var withinElement = $( element || window ),
+ isWindow = $.isWindow( withinElement[0] );
+ return {
+ element: withinElement,
+ isWindow: isWindow,
+ offset: withinElement.offset() || { left: 0, top: 0 },
+ scrollLeft: withinElement.scrollLeft(),
+ scrollTop: withinElement.scrollTop(),
+ width: isWindow ? withinElement.width() : withinElement.outerWidth(),
+ height: isWindow ? withinElement.height() : withinElement.outerHeight()
+ };
+ }
+};
+
+$.fn.position = function( options ) {
+ if ( !options || !options.of ) {
+ return _position.apply( this, arguments );
+ }
+
+ // make a copy, we don't want to modify arguments
+ options = $.extend( {}, options );
+
+ var atOffset, targetWidth, targetHeight, targetOffset, basePosition,
+ target = $( options.of ),
+ within = $.position.getWithinInfo( options.within ),
+ scrollInfo = $.position.getScrollInfo( within ),
+ targetElem = target[0],
+ collision = ( options.collision || "flip" ).split( " " ),
+ offsets = {};
+
+ if ( targetElem.nodeType === 9 ) {
+ targetWidth = target.width();
+ targetHeight = target.height();
+ targetOffset = { top: 0, left: 0 };
+ } else if ( $.isWindow( targetElem ) ) {
+ targetWidth = target.width();
+ targetHeight = target.height();
+ targetOffset = { top: target.scrollTop(), left: target.scrollLeft() };
+ } else if ( targetElem.preventDefault ) {
+ // force left top to allow flipping
+ options.at = "left top";
+ targetWidth = targetHeight = 0;
+ targetOffset = { top: targetElem.pageY, left: targetElem.pageX };
+ } else {
+ targetWidth = target.outerWidth();
+ targetHeight = target.outerHeight();
+ targetOffset = target.offset();
+ }
+ // clone to reuse original targetOffset later
+ basePosition = $.extend( {}, targetOffset );
+
+ // force my and at to have valid horizontal and vertical positions
+ // if a value is missing or invalid, it will be converted to center
+ $.each( [ "my", "at" ], function() {
+ var pos = ( options[ this ] || "" ).split( " " ),
+ horizontalOffset,
+ verticalOffset;
+
+ if ( pos.length === 1) {
+ pos = rhorizontal.test( pos[ 0 ] ) ?
+ pos.concat( [ "center" ] ) :
+ rvertical.test( pos[ 0 ] ) ?
+ [ "center" ].concat( pos ) :
+ [ "center", "center" ];
+ }
+ pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center";
+ pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center";
+
+ // calculate offsets
+ horizontalOffset = roffset.exec( pos[ 0 ] );
+ verticalOffset = roffset.exec( pos[ 1 ] );
+ offsets[ this ] = [
+ horizontalOffset ? horizontalOffset[ 0 ] : 0,
+ verticalOffset ? verticalOffset[ 0 ] : 0
+ ];
+
+ // reduce to just the positions without the offsets
+ options[ this ] = [
+ rposition.exec( pos[ 0 ] )[ 0 ],
+ rposition.exec( pos[ 1 ] )[ 0 ]
+ ];
+ });
+
+ // normalize collision option
+ if ( collision.length === 1 ) {
+ collision[ 1 ] = collision[ 0 ];
+ }
+
+ if ( options.at[ 0 ] === "right" ) {
+ basePosition.left += targetWidth;
+ } else if ( options.at[ 0 ] === "center" ) {
+ basePosition.left += targetWidth / 2;
+ }
+
+ if ( options.at[ 1 ] === "bottom" ) {
+ basePosition.top += targetHeight;
+ } else if ( options.at[ 1 ] === "center" ) {
+ basePosition.top += targetHeight / 2;
+ }
+
+ atOffset = getOffsets( offsets.at, targetWidth, targetHeight );
+ basePosition.left += atOffset[ 0 ];
+ basePosition.top += atOffset[ 1 ];
+
+ return this.each(function() {
+ var collisionPosition, using,
+ elem = $( this ),
+ elemWidth = elem.outerWidth(),
+ elemHeight = elem.outerHeight(),
+ marginLeft = parseCss( this, "marginLeft" ),
+ marginTop = parseCss( this, "marginTop" ),
+ collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) + scrollInfo.width,
+ collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) + scrollInfo.height,
+ position = $.extend( {}, basePosition ),
+ myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() );
+
+ if ( options.my[ 0 ] === "right" ) {
+ position.left -= elemWidth;
+ } else if ( options.my[ 0 ] === "center" ) {
+ position.left -= elemWidth / 2;
+ }
+
+ if ( options.my[ 1 ] === "bottom" ) {
+ position.top -= elemHeight;
+ } else if ( options.my[ 1 ] === "center" ) {
+ position.top -= elemHeight / 2;
+ }
+
+ position.left += myOffset[ 0 ];
+ position.top += myOffset[ 1 ];
+
+ // if the browser doesn't support fractions, then round for consistent results
+ if ( !$.support.offsetFractions ) {
+ position.left = round( position.left );
+ position.top = round( position.top );
+ }
+
+ collisionPosition = {
+ marginLeft: marginLeft,
+ marginTop: marginTop
+ };
+
+ $.each( [ "left", "top" ], function( i, dir ) {
+ if ( $.ui.position[ collision[ i ] ] ) {
+ $.ui.position[ collision[ i ] ][ dir ]( position, {
+ targetWidth: targetWidth,
+ targetHeight: targetHeight,
+ elemWidth: elemWidth,
+ elemHeight: elemHeight,
+ collisionPosition: collisionPosition,
+ collisionWidth: collisionWidth,
+ collisionHeight: collisionHeight,
+ offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ],
+ my: options.my,
+ at: options.at,
+ within: within,
+ elem : elem
+ });
+ }
+ });
+
+ if ( $.fn.bgiframe ) {
+ elem.bgiframe();
+ }
+
+ if ( options.using ) {
+ // adds feedback as second argument to using callback, if present
+ using = function( props ) {
+ var left = targetOffset.left - position.left,
+ right = left + targetWidth - elemWidth,
+ top = targetOffset.top - position.top,
+ bottom = top + targetHeight - elemHeight,
+ feedback = {
+ target: {
+ element: target,
+ left: targetOffset.left,
+ top: targetOffset.top,
+ width: targetWidth,
+ height: targetHeight
+ },
+ element: {
+ element: elem,
+ left: position.left,
+ top: position.top,
+ width: elemWidth,
+ height: elemHeight
+ },
+ horizontal: right < 0 ? "left" : left > 0 ? "right" : "center",
+ vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle"
+ };
+ if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) {
+ feedback.horizontal = "center";
+ }
+ if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) {
+ feedback.vertical = "middle";
+ }
+ if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) {
+ feedback.important = "horizontal";
+ } else {
+ feedback.important = "vertical";
+ }
+ options.using.call( this, props, feedback );
+ };
+ }
+
+ elem.offset( $.extend( position, { using: using } ) );
+ });
+};
+
+$.ui.position = {
+ fit: {
+ left: function( position, data ) {
+ var within = data.within,
+ withinOffset = within.isWindow ? within.scrollLeft : within.offset.left,
+ outerWidth = within.width,
+ collisionPosLeft = position.left - data.collisionPosition.marginLeft,
+ overLeft = withinOffset - collisionPosLeft,
+ overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset,
+ newOverRight;
+
+ // element is wider than within
+ if ( data.collisionWidth > outerWidth ) {
+ // element is initially over the left side of within
+ if ( overLeft > 0 && overRight <= 0 ) {
+ newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - withinOffset;
+ position.left += overLeft - newOverRight;
+ // element is initially over right side of within
+ } else if ( overRight > 0 && overLeft <= 0 ) {
+ position.left = withinOffset;
+ // element is initially over both left and right sides of within
+ } else {
+ if ( overLeft > overRight ) {
+ position.left = withinOffset + outerWidth - data.collisionWidth;
+ } else {
+ position.left = withinOffset;
+ }
+ }
+ // too far left -> align with left edge
+ } else if ( overLeft > 0 ) {
+ position.left += overLeft;
+ // too far right -> align with right edge
+ } else if ( overRight > 0 ) {
+ position.left -= overRight;
+ // adjust based on position and margin
+ } else {
+ position.left = max( position.left - collisionPosLeft, position.left );
+ }
+ },
+ top: function( position, data ) {
+ var within = data.within,
+ withinOffset = within.isWindow ? within.scrollTop : within.offset.top,
+ outerHeight = data.within.height,
+ collisionPosTop = position.top - data.collisionPosition.marginTop,
+ overTop = withinOffset - collisionPosTop,
+ overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset,
+ newOverBottom;
+
+ // element is taller than within
+ if ( data.collisionHeight > outerHeight ) {
+ // element is initially over the top of within
+ if ( overTop > 0 && overBottom <= 0 ) {
+ newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - withinOffset;
+ position.top += overTop - newOverBottom;
+ // element is initially over bottom of within
+ } else if ( overBottom > 0 && overTop <= 0 ) {
+ position.top = withinOffset;
+ // element is initially over both top and bottom of within
+ } else {
+ if ( overTop > overBottom ) {
+ position.top = withinOffset + outerHeight - data.collisionHeight;
+ } else {
+ position.top = withinOffset;
+ }
+ }
+ // too far up -> align with top
+ } else if ( overTop > 0 ) {
+ position.top += overTop;
+ // too far down -> align with bottom edge
+ } else if ( overBottom > 0 ) {
+ position.top -= overBottom;
+ // adjust based on position and margin
+ } else {
+ position.top = max( position.top - collisionPosTop, position.top );
+ }
+ }
+ },
+ flip: {
+ left: function( position, data ) {
+ var within = data.within,
+ withinOffset = within.offset.left + within.scrollLeft,
+ outerWidth = within.width,
+ offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left,
+ collisionPosLeft = position.left - data.collisionPosition.marginLeft,
+ overLeft = collisionPosLeft - offsetLeft,
+ overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft,
+ myOffset = data.my[ 0 ] === "left" ?
+ -data.elemWidth :
+ data.my[ 0 ] === "right" ?
+ data.elemWidth :
+ 0,
+ atOffset = data.at[ 0 ] === "left" ?
+ data.targetWidth :
+ data.at[ 0 ] === "right" ?
+ -data.targetWidth :
+ 0,
+ offset = -2 * data.offset[ 0 ],
+ newOverRight,
+ newOverLeft;
+
+ if ( overLeft < 0 ) {
+ newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - outerWidth - withinOffset;
+ if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) {
+ position.left += myOffset + atOffset + offset;
+ }
+ }
+ else if ( overRight > 0 ) {
+ newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + atOffset + offset - offsetLeft;
+ if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) {
+ position.left += myOffset + atOffset + offset;
+ }
+ }
+ },
+ top: function( position, data ) {
+ var within = data.within,
+ withinOffset = within.offset.top + within.scrollTop,
+ outerHeight = within.height,
+ offsetTop = within.isWindow ? within.scrollTop : within.offset.top,
+ collisionPosTop = position.top - data.collisionPosition.marginTop,
+ overTop = collisionPosTop - offsetTop,
+ overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop,
+ top = data.my[ 1 ] === "top",
+ myOffset = top ?
+ -data.elemHeight :
+ data.my[ 1 ] === "bottom" ?
+ data.elemHeight :
+ 0,
+ atOffset = data.at[ 1 ] === "top" ?
+ data.targetHeight :
+ data.at[ 1 ] === "bottom" ?
+ -data.targetHeight :
+ 0,
+ offset = -2 * data.offset[ 1 ],
+ newOverTop,
+ newOverBottom;
+ if ( overTop < 0 ) {
+ newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - outerHeight - withinOffset;
+ if ( ( position.top + myOffset + atOffset + offset) > overTop && ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) ) {
+ position.top += myOffset + atOffset + offset;
+ }
+ }
+ else if ( overBottom > 0 ) {
+ newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset + offset - offsetTop;
+ if ( ( position.top + myOffset + atOffset + offset) > overBottom && ( newOverTop > 0 || abs( newOverTop ) < overBottom ) ) {
+ position.top += myOffset + atOffset + offset;
+ }
+ }
+ }
+ },
+ flipfit: {
+ left: function() {
+ $.ui.position.flip.left.apply( this, arguments );
+ $.ui.position.fit.left.apply( this, arguments );
+ },
+ top: function() {
+ $.ui.position.flip.top.apply( this, arguments );
+ $.ui.position.fit.top.apply( this, arguments );
+ }
+ }
+};
+
+// fraction support test
+(function () {
+ var testElement, testElementParent, testElementStyle, offsetLeft, i,
+ body = document.getElementsByTagName( "body" )[ 0 ],
+ div = document.createElement( "div" );
+
+ //Create a "fake body" for testing based on method used in jQuery.support
+ testElement = document.createElement( body ? "div" : "body" );
+ testElementStyle = {
+ visibility: "hidden",
+ width: 0,
+ height: 0,
+ border: 0,
+ margin: 0,
+ background: "none"
+ };
+ if ( body ) {
+ $.extend( testElementStyle, {
+ position: "absolute",
+ left: "-1000px",
+ top: "-1000px"
+ });
+ }
+ for ( i in testElementStyle ) {
+ testElement.style[ i ] = testElementStyle[ i ];
+ }
+ testElement.appendChild( div );
+ testElementParent = body || document.documentElement;
+ testElementParent.insertBefore( testElement, testElementParent.firstChild );
+
+ div.style.cssText = "position: absolute; left: 10.7432222px;";
+
+ offsetLeft = $( div ).offset().left;
+ $.support.offsetFractions = offsetLeft > 10 && offsetLeft < 11;
+
+ testElement.innerHTML = "";
+ testElementParent.removeChild( testElement );
+})();
+
+// DEPRECATED
+if ( $.uiBackCompat !== false ) {
+ // offset option
+ (function( $ ) {
+ var _position = $.fn.position;
+ $.fn.position = function( options ) {
+ if ( !options || !options.offset ) {
+ return _position.call( this, options );
+ }
+ var offset = options.offset.split( " " ),
+ at = options.at.split( " " );
+ if ( offset.length === 1 ) {
+ offset[ 1 ] = offset[ 0 ];
+ }
+ if ( /^\d/.test( offset[ 0 ] ) ) {
+ offset[ 0 ] = "+" + offset[ 0 ];
+ }
+ if ( /^\d/.test( offset[ 1 ] ) ) {
+ offset[ 1 ] = "+" + offset[ 1 ];
+ }
+ if ( at.length === 1 ) {
+ if ( /left|center|right/.test( at[ 0 ] ) ) {
+ at[ 1 ] = "center";
+ } else {
+ at[ 1 ] = at[ 0 ];
+ at[ 0 ] = "center";
+ }
+ }
+ return _position.call( this, $.extend( options, {
+ at: at[ 0 ] + offset[ 0 ] + " " + at[ 1 ] + offset[ 1 ],
+ offset: undefined
+ } ) );
+ };
+ }( jQuery ) );
+}
+
+}( jQuery ) );
+(function( $, undefined ) {
+
+// number of pages in a slider
+// (how many times can you page up/down to go through the whole range)
+var numPages = 5;
+
+$.widget( "ui.slider", $.ui.mouse, {
+ version: "1.9.2",
+ widgetEventPrefix: "slide",
+
+ options: {
+ animate: false,
+ distance: 0,
+ max: 100,
+ min: 0,
+ orientation: "horizontal",
+ range: false,
+ step: 1,
+ value: 0,
+ values: null
+ },
+
+ _create: function() {
+ var i, handleCount,
+ o = this.options,
+ existingHandles = this.element.find( ".ui-slider-handle" ).addClass( "ui-state-default ui-corner-all" ),
+ handle = "<a class='ui-slider-handle ui-state-default ui-corner-all' href='#'></a>",
+ handles = [];
+
+ this._keySliding = false;
+ this._mouseSliding = false;
+ this._animateOff = true;
+ this._handleIndex = null;
+ this._detectOrientation();
+ this._mouseInit();
+
+ this.element
+ .addClass( "ui-slider" +
+ " ui-slider-" + this.orientation +
+ " ui-widget" +
+ " ui-widget-content" +
+ " ui-corner-all" +
+ ( o.disabled ? " ui-slider-disabled ui-disabled" : "" ) );
+
+ this.range = $([]);
+
+ if ( o.range ) {
+ if ( o.range === true ) {
+ if ( !o.values ) {
+ o.values = [ this._valueMin(), this._valueMin() ];
+ }
+ if ( o.values.length && o.values.length !== 2 ) {
+ o.values = [ o.values[0], o.values[0] ];
+ }
+ }
+
+ this.range = $( "<div></div>" )
+ .appendTo( this.element )
+ .addClass( "ui-slider-range" +
+ // note: this isn't the most fittingly semantic framework class for this element,
+ // but worked best visually with a variety of themes
+ " ui-widget-header" +
+ ( ( o.range === "min" || o.range === "max" ) ? " ui-slider-range-" + o.range : "" ) );
+ }
+
+ handleCount = ( o.values && o.values.length ) || 1;
+
+ for ( i = existingHandles.length; i < handleCount; i++ ) {
+ handles.push( handle );
+ }
+
+ this.handles = existingHandles.add( $( handles.join( "" ) ).appendTo( this.element ) );
+
+ this.handle = this.handles.eq( 0 );
+
+ this.handles.add( this.range ).filter( "a" )
+ .click(function( event ) {
+ event.preventDefault();
+ })
+ .mouseenter(function() {
+ if ( !o.disabled ) {
+ $( this ).addClass( "ui-state-hover" );
+ }
+ })
+ .mouseleave(function() {
+ $( this ).removeClass( "ui-state-hover" );
+ })
+ .focus(function() {
+ if ( !o.disabled ) {
+ $( ".ui-slider .ui-state-focus" ).removeClass( "ui-state-focus" );
+ $( this ).addClass( "ui-state-focus" );
+ } else {
+ $( this ).blur();
+ }
+ })
+ .blur(function() {
+ $( this ).removeClass( "ui-state-focus" );
+ });
+
+ this.handles.each(function( i ) {
+ $( this ).data( "ui-slider-handle-index", i );
+ });
+
+ this._on( this.handles, {
+ keydown: function( event ) {
+ var allowed, curVal, newVal, step,
+ index = $( event.target ).data( "ui-slider-handle-index" );
+
+ switch ( event.keyCode ) {
+ case $.ui.keyCode.HOME:
+ case $.ui.keyCode.END:
+ case $.ui.keyCode.PAGE_UP:
+ case $.ui.keyCode.PAGE_DOWN:
+ case $.ui.keyCode.UP:
+ case $.ui.keyCode.RIGHT:
+ case $.ui.keyCode.DOWN:
+ case $.ui.keyCode.LEFT:
+ event.preventDefault();
+ if ( !this._keySliding ) {
+ this._keySliding = true;
+ $( event.target ).addClass( "ui-state-active" );
+ allowed = this._start( event, index );
+ if ( allowed === false ) {
+ return;
+ }
+ }
+ break;
+ }
+
+ step = this.options.step;
+ if ( this.options.values && this.options.values.length ) {
+ curVal = newVal = this.values( index );
+ } else {
+ curVal = newVal = this.value();
+ }
+
+ switch ( event.keyCode ) {
+ case $.ui.keyCode.HOME:
+ newVal = this._valueMin();
+ break;
+ case $.ui.keyCode.END:
+ newVal = this._valueMax();
+ break;
+ case $.ui.keyCode.PAGE_UP:
+ newVal = this._trimAlignValue( curVal + ( (this._valueMax() - this._valueMin()) / numPages ) );
+ break;
+ case $.ui.keyCode.PAGE_DOWN:
+ newVal = this._trimAlignValue( curVal - ( (this._valueMax() - this._valueMin()) / numPages ) );
+ break;
+ case $.ui.keyCode.UP:
+ case $.ui.keyCode.RIGHT:
+ if ( curVal === this._valueMax() ) {
+ return;
+ }
+ newVal = this._trimAlignValue( curVal + step );
+ break;
+ case $.ui.keyCode.DOWN:
+ case $.ui.keyCode.LEFT:
+ if ( curVal === this._valueMin() ) {
+ return;
+ }
+ newVal = this._trimAlignValue( curVal - step );
+ break;
+ }
+
+ this._slide( event, index, newVal );
+ },
+ keyup: function( event ) {
+ var index = $( event.target ).data( "ui-slider-handle-index" );
+
+ if ( this._keySliding ) {
+ this._keySliding = false;
+ this._stop( event, index );
+ this._change( event, index );
+ $( event.target ).removeClass( "ui-state-active" );
+ }
+ }
+ });
+
+ this._refreshValue();
+
+ this._animateOff = false;
+ },
+
+ _destroy: function() {
+ this.handles.remove();
+ this.range.remove();
+
+ this.element
+ .removeClass( "ui-slider" +
+ " ui-slider-horizontal" +
+ " ui-slider-vertical" +
+ " ui-slider-disabled" +
+ " ui-widget" +
+ " ui-widget-content" +
+ " ui-corner-all" );
+
+ this._mouseDestroy();
+ },
+
+ _mouseCapture: function( event ) {
+ var position, normValue, distance, closestHandle, index, allowed, offset, mouseOverHandle,
+ that = this,
+ o = this.options;
+
+ if ( o.disabled ) {
+ return false;
+ }
+
+ this.elementSize = {
+ width: this.element.outerWidth(),
+ height: this.element.outerHeight()
+ };
+ this.elementOffset = this.element.offset();
+
+ position = { x: event.pageX, y: event.pageY };
+ normValue = this._normValueFromMouse( position );
+ distance = this._valueMax() - this._valueMin() + 1;
+ this.handles.each(function( i ) {
+ var thisDistance = Math.abs( normValue - that.values(i) );
+ if ( distance > thisDistance ) {
+ distance = thisDistance;
+ closestHandle = $( this );
+ index = i;
+ }
+ });
+
+ // workaround for bug #3736 (if both handles of a range are at 0,
+ // the first is always used as the one with least distance,
+ // and moving it is obviously prevented by preventing negative ranges)
+ if( o.range === true && this.values(1) === o.min ) {
+ index += 1;
+ closestHandle = $( this.handles[index] );
+ }
+
+ allowed = this._start( event, index );
+ if ( allowed === false ) {
+ return false;
+ }
+ this._mouseSliding = true;
+
+ this._handleIndex = index;
+
+ closestHandle
+ .addClass( "ui-state-active" )
+ .focus();
+
+ offset = closestHandle.offset();
+ mouseOverHandle = !$( event.target ).parents().andSelf().is( ".ui-slider-handle" );
+ this._clickOffset = mouseOverHandle ? { left: 0, top: 0 } : {
+ left: event.pageX - offset.left - ( closestHandle.width() / 2 ),
+ top: event.pageY - offset.top -
+ ( closestHandle.height() / 2 ) -
+ ( parseInt( closestHandle.css("borderTopWidth"), 10 ) || 0 ) -
+ ( parseInt( closestHandle.css("borderBottomWidth"), 10 ) || 0) +
+ ( parseInt( closestHandle.css("marginTop"), 10 ) || 0)
+ };
+
+ if ( !this.handles.hasClass( "ui-state-hover" ) ) {
+ this._slide( event, index, normValue );
+ }
+ this._animateOff = true;
+ return true;
+ },
+
+ _mouseStart: function() {
+ return true;
+ },
+
+ _mouseDrag: function( event ) {
+ var position = { x: event.pageX, y: event.pageY },
+ normValue = this._normValueFromMouse( position );
+
+ this._slide( event, this._handleIndex, normValue );
+
+ return false;
+ },
+
+ _mouseStop: function( event ) {
+ this.handles.removeClass( "ui-state-active" );
+ this._mouseSliding = false;
+
+ this._stop( event, this._handleIndex );
+ this._change( event, this._handleIndex );
+
+ this._handleIndex = null;
+ this._clickOffset = null;
+ this._animateOff = false;
+
+ return false;
+ },
+
+ _detectOrientation: function() {
+ this.orientation = ( this.options.orientation === "vertical" ) ? "vertical" : "horizontal";
+ },
+
+ _normValueFromMouse: function( position ) {
+ var pixelTotal,
+ pixelMouse,
+ percentMouse,
+ valueTotal,
+ valueMouse;
+
+ if ( this.orientation === "horizontal" ) {
+ pixelTotal = this.elementSize.width;
+ pixelMouse = position.x - this.elementOffset.left - ( this._clickOffset ? this._clickOffset.left : 0 );
+ } else {
+ pixelTotal = this.elementSize.height;
+ pixelMouse = position.y - this.elementOffset.top - ( this._clickOffset ? this._clickOffset.top : 0 );
+ }
+
+ percentMouse = ( pixelMouse / pixelTotal );
+ if ( percentMouse > 1 ) {
+ percentMouse = 1;
+ }
+ if ( percentMouse < 0 ) {
+ percentMouse = 0;
+ }
+ if ( this.orientation === "vertical" ) {
+ percentMouse = 1 - percentMouse;
+ }
+
+ valueTotal = this._valueMax() - this._valueMin();
+ valueMouse = this._valueMin() + percentMouse * valueTotal;
+
+ return this._trimAlignValue( valueMouse );
+ },
+
+ _start: function( event, index ) {
+ var uiHash = {
+ handle: this.handles[ index ],
+ value: this.value()
+ };
+ if ( this.options.values && this.options.values.length ) {
+ uiHash.value = this.values( index );
+ uiHash.values = this.values();
+ }
+ return this._trigger( "start", event, uiHash );
+ },
+
+ _slide: function( event, index, newVal ) {
+ var otherVal,
+ newValues,
+ allowed;
+
+ if ( this.options.values && this.options.values.length ) {
+ otherVal = this.values( index ? 0 : 1 );
+
+ if ( ( this.options.values.length === 2 && this.options.range === true ) &&
+ ( ( index === 0 && newVal > otherVal) || ( index === 1 && newVal < otherVal ) )
+ ) {
+ newVal = otherVal;
+ }
+
+ if ( newVal !== this.values( index ) ) {
+ newValues = this.values();
+ newValues[ index ] = newVal;
+ // A slide can be canceled by returning false from the slide callback
+ allowed = this._trigger( "slide", event, {
+ handle: this.handles[ index ],
+ value: newVal,
+ values: newValues
+ } );
+ otherVal = this.values( index ? 0 : 1 );
+ if ( allowed !== false ) {
+ this.values( index, newVal, true );
+ }
+ }
+ } else {
+ if ( newVal !== this.value() ) {
+ // A slide can be canceled by returning false from the slide callback
+ allowed = this._trigger( "slide", event, {
+ handle: this.handles[ index ],
+ value: newVal
+ } );
+ if ( allowed !== false ) {
+ this.value( newVal );
+ }
+ }
+ }
+ },
+
+ _stop: function( event, index ) {
+ var uiHash = {
+ handle: this.handles[ index ],
+ value: this.value()
+ };
+ if ( this.options.values && this.options.values.length ) {
+ uiHash.value = this.values( index );
+ uiHash.values = this.values();
+ }
+
+ this._trigger( "stop", event, uiHash );
+ },
+
+ _change: function( event, index ) {
+ if ( !this._keySliding && !this._mouseSliding ) {
+ var uiHash = {
+ handle: this.handles[ index ],
+ value: this.value()
+ };
+ if ( this.options.values && this.options.values.length ) {
+ uiHash.value = this.values( index );
+ uiHash.values = this.values();
+ }
+
+ this._trigger( "change", event, uiHash );
+ }
+ },
+
+ value: function( newValue ) {
+ if ( arguments.length ) {
+ this.options.value = this._trimAlignValue( newValue );
+ this._refreshValue();
+ this._change( null, 0 );
+ return;
+ }
+
+ return this._value();
+ },
+
+ values: function( index, newValue ) {
+ var vals,
+ newValues,
+ i;
+
+ if ( arguments.length > 1 ) {
+ this.options.values[ index ] = this._trimAlignValue( newValue );
+ this._refreshValue();
+ this._change( null, index );
+ return;
+ }
+
+ if ( arguments.length ) {
+ if ( $.isArray( arguments[ 0 ] ) ) {
+ vals = this.options.values;
+ newValues = arguments[ 0 ];
+ for ( i = 0; i < vals.length; i += 1 ) {
+ vals[ i ] = this._trimAlignValue( newValues[ i ] );
+ this._change( null, i );
+ }
+ this._refreshValue();
+ } else {
+ if ( this.options.values && this.options.values.length ) {
+ return this._values( index );
+ } else {
+ return this.value();
+ }
+ }
+ } else {
+ return this._values();
+ }
+ },
+
+ _setOption: function( key, value ) {
+ var i,
+ valsLength = 0;
+
+ if ( $.isArray( this.options.values ) ) {
+ valsLength = this.options.values.length;
+ }
+
+ $.Widget.prototype._setOption.apply( this, arguments );
+
+ switch ( key ) {
+ case "disabled":
+ if ( value ) {
+ this.handles.filter( ".ui-state-focus" ).blur();
+ this.handles.removeClass( "ui-state-hover" );
+ this.handles.prop( "disabled", true );
+ this.element.addClass( "ui-disabled" );
+ } else {
+ this.handles.prop( "disabled", false );
+ this.element.removeClass( "ui-disabled" );
+ }
+ break;
+ case "orientation":
+ this._detectOrientation();
+ this.element
+ .removeClass( "ui-slider-horizontal ui-slider-vertical" )
+ .addClass( "ui-slider-" + this.orientation );
+ this._refreshValue();
+ break;
+ case "value":
+ this._animateOff = true;
+ this._refreshValue();
+ this._change( null, 0 );
+ this._animateOff = false;
+ break;
+ case "values":
+ this._animateOff = true;
+ this._refreshValue();
+ for ( i = 0; i < valsLength; i += 1 ) {
+ this._change( null, i );
+ }
+ this._animateOff = false;
+ break;
+ case "min":
+ case "max":
+ this._animateOff = true;
+ this._refreshValue();
+ this._animateOff = false;
+ break;
+ }
+ },
+
+ //internal value getter
+ // _value() returns value trimmed by min and max, aligned by step
+ _value: function() {
+ var val = this.options.value;
+ val = this._trimAlignValue( val );
+
+ return val;
+ },
+
+ //internal values getter
+ // _values() returns array of values trimmed by min and max, aligned by step
+ // _values( index ) returns single value trimmed by min and max, aligned by step
+ _values: function( index ) {
+ var val,
+ vals,
+ i;
+
+ if ( arguments.length ) {
+ val = this.options.values[ index ];
+ val = this._trimAlignValue( val );
+
+ return val;
+ } else {
+ // .slice() creates a copy of the array
+ // this copy gets trimmed by min and max and then returned
+ vals = this.options.values.slice();
+ for ( i = 0; i < vals.length; i+= 1) {
+ vals[ i ] = this._trimAlignValue( vals[ i ] );
+ }
+
+ return vals;
+ }
+ },
+
+ // returns the step-aligned value that val is closest to, between (inclusive) min and max
+ _trimAlignValue: function( val ) {
+ if ( val <= this._valueMin() ) {
+ return this._valueMin();
+ }
+ if ( val >= this._valueMax() ) {
+ return this._valueMax();
+ }
+ var step = ( this.options.step > 0 ) ? this.options.step : 1,
+ valModStep = (val - this._valueMin()) % step,
+ alignValue = val - valModStep;
+
+ if ( Math.abs(valModStep) * 2 >= step ) {
+ alignValue += ( valModStep > 0 ) ? step : ( -step );
+ }
+
+ // Since JavaScript has problems with large floats, round
+ // the final value to 5 digits after the decimal point (see #4124)
+ return parseFloat( alignValue.toFixed(5) );
+ },
+
+ _valueMin: function() {
+ return this.options.min;
+ },
+
+ _valueMax: function() {
+ return this.options.max;
+ },
+
+ _refreshValue: function() {
+ var lastValPercent, valPercent, value, valueMin, valueMax,
+ oRange = this.options.range,
+ o = this.options,
+ that = this,
+ animate = ( !this._animateOff ) ? o.animate : false,
+ _set = {};
+
+ if ( this.options.values && this.options.values.length ) {
+ this.handles.each(function( i ) {
+ valPercent = ( that.values(i) - that._valueMin() ) / ( that._valueMax() - that._valueMin() ) * 100;
+ _set[ that.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
+ $( this ).stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );
+ if ( that.options.range === true ) {
+ if ( that.orientation === "horizontal" ) {
+ if ( i === 0 ) {
+ that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { left: valPercent + "%" }, o.animate );
+ }
+ if ( i === 1 ) {
+ that.range[ animate ? "animate" : "css" ]( { width: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
+ }
+ } else {
+ if ( i === 0 ) {
+ that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { bottom: ( valPercent ) + "%" }, o.animate );
+ }
+ if ( i === 1 ) {
+ that.range[ animate ? "animate" : "css" ]( { height: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
+ }
+ }
+ }
+ lastValPercent = valPercent;
+ });
+ } else {
+ value = this.value();
+ valueMin = this._valueMin();
+ valueMax = this._valueMax();
+ valPercent = ( valueMax !== valueMin ) ?
+ ( value - valueMin ) / ( valueMax - valueMin ) * 100 :
+ 0;
+ _set[ this.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
+ this.handle.stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );
+
+ if ( oRange === "min" && this.orientation === "horizontal" ) {
+ this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { width: valPercent + "%" }, o.animate );
+ }
+ if ( oRange === "max" && this.orientation === "horizontal" ) {
+ this.range[ animate ? "animate" : "css" ]( { width: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } );
+ }
+ if ( oRange === "min" && this.orientation === "vertical" ) {
+ this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { height: valPercent + "%" }, o.animate );
+ }
+ if ( oRange === "max" && this.orientation === "vertical" ) {
+ this.range[ animate ? "animate" : "css" ]( { height: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } );
+ }
+ }
+ }
+
+});
+
+}(jQuery));
+(function( $ ) {
+
+var increments = 0;
+
+function addDescribedBy( elem, id ) {
+ var describedby = (elem.attr( "aria-describedby" ) || "").split( /\s+/ );
+ describedby.push( id );
+ elem
+ .data( "ui-tooltip-id", id )
+ .attr( "aria-describedby", $.trim( describedby.join( " " ) ) );
+}
+
+function removeDescribedBy( elem ) {
+ var id = elem.data( "ui-tooltip-id" ),
+ describedby = (elem.attr( "aria-describedby" ) || "").split( /\s+/ ),
+ index = $.inArray( id, describedby );
+ if ( index !== -1 ) {
+ describedby.splice( index, 1 );
+ }
+
+ elem.removeData( "ui-tooltip-id" );
+ describedby = $.trim( describedby.join( " " ) );
+ if ( describedby ) {
+ elem.attr( "aria-describedby", describedby );
+ } else {
+ elem.removeAttr( "aria-describedby" );
+ }
+}
+
+$.widget( "ui.tooltip", {
+ version: "1.9.2",
+ options: {
+ content: function() {
+ return $( this ).attr( "title" );
+ },
+ hide: true,
+ // Disabled elements have inconsistent behavior across browsers (#8661)
+ items: "[title]:not([disabled])",
+ position: {
+ my: "left top+15",
+ at: "left bottom",
+ collision: "flipfit flip"
+ },
+ show: true,
+ tooltipClass: null,
+ track: false,
+
+ // callbacks
+ close: null,
+ open: null
+ },
+
+ _create: function() {
+ this._on({
+ mouseover: "open",
+ focusin: "open"
+ });
+
+ // IDs of generated tooltips, needed for destroy
+ this.tooltips = {};
+ // IDs of parent tooltips where we removed the title attribute
+ this.parents = {};
+
+ if ( this.options.disabled ) {
+ this._disable();
+ }
+ },
+
+ _setOption: function( key, value ) {
+ var that = this;
+
+ if ( key === "disabled" ) {
+ this[ value ? "_disable" : "_enable" ]();
+ this.options[ key ] = value;
+ // disable element style changes
+ return;
+ }
+
+ this._super( key, value );
+
+ if ( key === "content" ) {
+ $.each( this.tooltips, function( id, element ) {
+ that._updateContent( element );
+ });
+ }
+ },
+
+ _disable: function() {
+ var that = this;
+
+ // close open tooltips
+ $.each( this.tooltips, function( id, element ) {
+ var event = $.Event( "blur" );
+ event.target = event.currentTarget = element[0];
+ that.close( event, true );
+ });
+
+ // remove title attributes to prevent native tooltips
+ this.element.find( this.options.items ).andSelf().each(function() {
+ var element = $( this );
+ if ( element.is( "[title]" ) ) {
+ element
+ .data( "ui-tooltip-title", element.attr( "title" ) )
+ .attr( "title", "" );
+ }
+ });
+ },
+
+ _enable: function() {
+ // restore title attributes
+ this.element.find( this.options.items ).andSelf().each(function() {
+ var element = $( this );
+ if ( element.data( "ui-tooltip-title" ) ) {
+ element.attr( "title", element.data( "ui-tooltip-title" ) );
+ }
+ });
+ },
+
+ open: function( event ) {
+ var that = this,
+ target = $( event ? event.target : this.element )
+ // we need closest here due to mouseover bubbling,
+ // but always pointing at the same event target
+ .closest( this.options.items );
+
+ // No element to show a tooltip for or the tooltip is already open
+ if ( !target.length || target.data( "ui-tooltip-id" ) ) {
+ return;
+ }
+
+ if ( target.attr( "title" ) ) {
+ target.data( "ui-tooltip-title", target.attr( "title" ) );
+ }
+
+ target.data( "ui-tooltip-open", true );
+
+ // kill parent tooltips, custom or native, for hover
+ if ( event && event.type === "mouseover" ) {
+ target.parents().each(function() {
+ var parent = $( this ),
+ blurEvent;
+ if ( parent.data( "ui-tooltip-open" ) ) {
+ blurEvent = $.Event( "blur" );
+ blurEvent.target = blurEvent.currentTarget = this;
+ that.close( blurEvent, true );
+ }
+ if ( parent.attr( "title" ) ) {
+ parent.uniqueId();
+ that.parents[ this.id ] = {
+ element: this,
+ title: parent.attr( "title" )
+ };
+ parent.attr( "title", "" );
+ }
+ });
+ }
+
+ this._updateContent( target, event );
+ },
+
+ _updateContent: function( target, event ) {
+ var content,
+ contentOption = this.options.content,
+ that = this,
+ eventType = event ? event.type : null;
+
+ if ( typeof contentOption === "string" ) {
+ return this._open( event, target, contentOption );
+ }
+
+ content = contentOption.call( target[0], function( response ) {
+ // ignore async response if tooltip was closed already
+ if ( !target.data( "ui-tooltip-open" ) ) {
+ return;
+ }
+ // IE may instantly serve a cached response for ajax requests
+ // delay this call to _open so the other call to _open runs first
+ that._delay(function() {
+ // jQuery creates a special event for focusin when it doesn't
+ // exist natively. To improve performance, the native event
+ // object is reused and the type is changed. Therefore, we can't
+ // rely on the type being correct after the event finished
+ // bubbling, so we set it back to the previous value. (#8740)
+ if ( event ) {
+ event.type = eventType;
+ }
+ this._open( event, target, response );
+ });
+ });
+ if ( content ) {
+ this._open( event, target, content );
+ }
+ },
+
+ _open: function( event, target, content ) {
+ var tooltip, events, delayedShow,
+ positionOption = $.extend( {}, this.options.position );
+
+ if ( !content ) {
+ return;
+ }
+
+ // Content can be updated multiple times. If the tooltip already
+ // exists, then just update the content and bail.
+ tooltip = this._find( target );
+ if ( tooltip.length ) {
+ tooltip.find( ".ui-tooltip-content" ).html( content );
+ return;
+ }
+
+ // if we have a title, clear it to prevent the native tooltip
+ // we have to check first to avoid defining a title if none exists
+ // (we don't want to cause an element to start matching [title])
+ //
+ // We use removeAttr only for key events, to allow IE to export the correct
+ // accessible attributes. For mouse events, set to empty string to avoid
+ // native tooltip showing up (happens only when removing inside mouseover).
+ if ( target.is( "[title]" ) ) {
+ if ( event && event.type === "mouseover" ) {
+ target.attr( "title", "" );
+ } else {
+ target.removeAttr( "title" );
+ }
+ }
+
+ tooltip = this._tooltip( target );
+ addDescribedBy( target, tooltip.attr( "id" ) );
+ tooltip.find( ".ui-tooltip-content" ).html( content );
+
+ function position( event ) {
+ positionOption.of = event;
+ if ( tooltip.is( ":hidden" ) ) {
+ return;
+ }
+ tooltip.position( positionOption );
+ }
+ if ( this.options.track && event && /^mouse/.test( event.type ) ) {
+ this._on( this.document, {
+ mousemove: position
+ });
+ // trigger once to override element-relative positioning
+ position( event );
+ } else {
+ tooltip.position( $.extend({
+ of: target
+ }, this.options.position ) );
+ }
+
+ tooltip.hide();
+
+ this._show( tooltip, this.options.show );
+ // Handle tracking tooltips that are shown with a delay (#8644). As soon
+ // as the tooltip is visible, position the tooltip using the most recent
+ // event.
+ if ( this.options.show && this.options.show.delay ) {
+ delayedShow = setInterval(function() {
+ if ( tooltip.is( ":visible" ) ) {
+ position( positionOption.of );
+ clearInterval( delayedShow );
+ }
+ }, $.fx.interval );
+ }
+
+ this._trigger( "open", event, { tooltip: tooltip } );
+
+ events = {
+ keyup: function( event ) {
+ if ( event.keyCode === $.ui.keyCode.ESCAPE ) {
+ var fakeEvent = $.Event(event);
+ fakeEvent.currentTarget = target[0];
+ this.close( fakeEvent, true );
+ }
+ },
+ remove: function() {
+ this._removeTooltip( tooltip );
+ }
+ };
+ if ( !event || event.type === "mouseover" ) {
+ events.mouseleave = "close";
+ }
+ if ( !event || event.type === "focusin" ) {
+ events.focusout = "close";
+ }
+ this._on( true, target, events );
+ },
+
+ close: function( event ) {
+ var that = this,
+ target = $( event ? event.currentTarget : this.element ),
+ tooltip = this._find( target );
+
+ // disabling closes the tooltip, so we need to track when we're closing
+ // to avoid an infinite loop in case the tooltip becomes disabled on close
+ if ( this.closing ) {
+ return;
+ }
+
+ // only set title if we had one before (see comment in _open())
+ if ( target.data( "ui-tooltip-title" ) ) {
+ target.attr( "title", target.data( "ui-tooltip-title" ) );
+ }
+
+ removeDescribedBy( target );
+
+ tooltip.stop( true );
+ this._hide( tooltip, this.options.hide, function() {
+ that._removeTooltip( $( this ) );
+ });
+
+ target.removeData( "ui-tooltip-open" );
+ this._off( target, "mouseleave focusout keyup" );
+ // Remove 'remove' binding only on delegated targets
+ if ( target[0] !== this.element[0] ) {
+ this._off( target, "remove" );
+ }
+ this._off( this.document, "mousemove" );
+
+ if ( event && event.type === "mouseleave" ) {
+ $.each( this.parents, function( id, parent ) {
+ $( parent.element ).attr( "title", parent.title );
+ delete that.parents[ id ];
+ });
+ }
+
+ this.closing = true;
+ this._trigger( "close", event, { tooltip: tooltip } );
+ this.closing = false;
+ },
+
+ _tooltip: function( element ) {
+ var id = "ui-tooltip-" + increments++,
+ tooltip = $( "<div>" )
+ .attr({
+ id: id,
+ role: "tooltip"
+ })
+ .addClass( "ui-tooltip ui-widget ui-corner-all ui-widget-content " +
+ ( this.options.tooltipClass || "" ) );
+ $( "<div>" )
+ .addClass( "ui-tooltip-content" )
+ .appendTo( tooltip );
+ tooltip.appendTo( this.document[0].body );
+ if ( $.fn.bgiframe ) {
+ tooltip.bgiframe();
+ }
+ this.tooltips[ id ] = element;
+ return tooltip;
+ },
+
+ _find: function( target ) {
+ var id = target.data( "ui-tooltip-id" );
+ return id ? $( "#" + id ) : $();
+ },
+
+ _removeTooltip: function( tooltip ) {
+ tooltip.remove();
+ delete this.tooltips[ tooltip.attr( "id" ) ];
+ },
+
+ _destroy: function() {
+ var that = this;
+
+ // close open tooltips
+ $.each( this.tooltips, function( id, element ) {
+ // Delegate to close method to handle common cleanup
+ var event = $.Event( "blur" );
+ event.target = event.currentTarget = element[0];
+ that.close( event, true );
+
+ // Remove immediately; destroying an open tooltip doesn't use the
+ // hide animation
+ $( "#" + id ).remove();
+
+ // Restore the title
+ if ( element.data( "ui-tooltip-title" ) ) {
+ element.attr( "title", element.data( "ui-tooltip-title" ) );
+ element.removeData( "ui-tooltip-title" );
+ }
+ });
+ }
+});
+
+}( jQuery ) );
diff --git a/content/injections/jquery.js b/content/injections/jquery.js
new file mode 100644
index 0000000..198b3ff
--- /dev/null
+++ b/content/injections/jquery.js
@@ -0,0 +1,4 @@
+/*! jQuery v1.7.1 jquery.com | jquery.org/license */
+(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cv(a){if(!ck[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){cl||(cl=c.createElement("iframe"),cl.frameBorder=cl.width=cl.height=0),b.appendChild(cl);if(!cm||!cl.createElement)cm=(cl.contentWindow||cl.contentDocument).document,cm.write((c.compatMode==="CSS1Compat"?"<!doctype html>":"")+"<html><body>"),cm.close();d=cm.createElement( [...]
+f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k<c.length;k++){l=A.exec(c[k])||[],m=l[1],n=(l[2]||"").split(".").sort(),s=f.event.special[m]| [...]
+{for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replac [...]
\ No newline at end of file
diff --git a/content/injections/surveys.widgets.js b/content/injections/surveys.widgets.js
new file mode 100644
index 0000000..4ccc653
--- /dev/null
+++ b/content/injections/surveys.widgets.js
@@ -0,0 +1,385 @@
+
+var surveys = {
+
+ question: {},
+ target: "",
+ decodedtarget: "",
+ url: "",
+ answer_value: null,
+ current_idx: 0,
+ submit_enabled: false,
+
+ report: function (msg, data) {
+ var _this = surveys;
+ if (typeof(data) === "object") {
+ data.question_id = _this.question.id;
+ data.url = _this.url;
+ data.target = _this.target;
+ }
+ wot.post("surveyswidget", msg, data);
+ },
+
+ extract_data: function(encoded) {
+ var res = null;
+ try {
+ if(!encoded) return null;
+ var decoded = atob(encoded);
+ if(!decoded) return null;
+
+ res = JSON.parse(decoded);
+ if(!(res && res.question)) return null;
+
+ } catch (e) {
+ console.error("Exception when extracting data in surveys.extract_data()", e);
+ }
+ return res;
+ },
+
+ slider: {
+
+ options_map: {},
+ options: [],
+ max_tick_width: 45,
+ $slider: null,
+
+ prepare_values: function (options) {
+ // makes an array of values for the slider and map them to original options
+
+ var _this = surveys.slider,
+ cnt = 1;
+
+ _this.options = [0]; // add default value
+ _this.options_map[0] = {value: null, text: " "};
+
+ for(var i in options) {
+ _this.options.push(cnt);
+ _this.options_map[cnt] = options[i];
+ cnt++;
+ }
+
+ return _this.options;
+ },
+
+ get_label_by_index: function(index) {
+ var _this = surveys.slider,
+ map = _this.options_map;
+
+ return (map[index] && map[index]['text'] !== undefined) ? map[index]['text'] : "";
+ },
+
+ get_value_by_index: function(index) {
+ var _this = surveys.slider,
+ map = _this.options_map;
+
+ return (map[index] && map[index]['value'] !== undefined) ? map[index]['value'] : "";
+ },
+
+
+ set_value_label: function (label, is_permanent) {
+ var $label = $(".surveys-slider-label");
+ $label.toggleClass("selected", is_permanent);
+ $label.toggleClass("isset", (label.trim().length > 0)); // don't show decoration if no text is in the label
+ $label.text(label);
+ },
+
+ update_slider_state: function (idx) {
+ var _this = surveys.slider,
+ label = _this.get_label_by_index(idx),
+ value = _this.get_value_by_index(idx);
+
+ surveys.current_idx = idx;
+ surveys.answer_value = value;
+ _this.set_value_label(label, true);
+
+ // modify appearance of the handle in "zero" position
+ $(".zero-tick").toggleClass("selected", (idx == 0));
+
+ $(".slider-tick").removeClass("selected");
+
+ if (idx > 0) {
+ $(".slider-tick:nth-child(" + (idx+1) + ")").addClass("selected");
+ }
+
+ surveys.submit_enabled = !!idx;
+ surveys.ui.update_submit_status();
+ },
+
+ on_slider_change: function (event, ui) {
+ var _this = surveys.slider;
+ _this.update_slider_state(ui.value);
+ },
+
+ on_slide: function(event, ui) {
+ var _this = surveys.slider;
+ _this.update_slider_state(ui.value);
+ },
+
+ on_slide_stop: function(event, ui) {
+ var _this = surveys;
+ wot.ga.fire_event(wot.ga.categories.FBL, wot.ga.actions.FBL_slidered, _this.target);
+ },
+
+ update_hover_status: function(idx, is_hovered) {
+ var _this = surveys.slider,
+ value_to_show = is_hovered ? idx : surveys.current_idx,
+ label = _this.get_label_by_index(value_to_show);
+
+ _this.set_value_label(label, !is_hovered);
+
+ if (idx > 0) {
+ $(".slider-tick:nth-child(" + (idx+1) + ")").toggleClass("hovered", is_hovered);
+ } else {
+ $(".zero-tick").toggleClass("hovered", is_hovered);
+ }
+ },
+
+ get_idx: function (element) {
+ return $(element).index();
+ },
+
+ on_enter: function (event) {
+ var _this = surveys.slider;
+ _this.update_hover_status(_this.get_idx(event.currentTarget), true);
+ },
+
+ on_leave: function (event) {
+ var _this = surveys.slider;
+ _this.update_hover_status(_this.get_idx(event.currentTarget), false);
+ },
+
+ on_click: function (event) {
+ var _this = surveys.slider;
+ _this.$slider.slider("value", _this.get_idx(event.currentTarget));
+ wot.ga.fire_event(wot.ga.categories.FBL, wot.ga.actions.FBL_directclick, _this.target);
+ },
+
+ build_slider: function() {
+ var _this = surveys.slider,
+ $slider = _this.$slider,
+ items_count = _this.options.length,
+ tick_ws,
+ $tick_container = $(".ticks-container");
+
+ var tick_width = Math.round(($slider.width() - items_count) / (items_count - 1)); // take into account border width also
+
+ tick_ws = String(tick_width) + "px";
+
+ // create ticks only if there are more than 3 options. 2 options are represented by edge-values of the slider
+ // and one additional option is a default value ("undefined")
+ if (items_count > 1) {
+ for(var i=0; i < items_count - 1; i++) {
+ $tick_container.append("<div class='slider-tick'/>");
+ }
+ }
+
+ var $ticks = $(".slider-tick"),
+ $zerotick = $(".zero-tick");
+
+ // add ticks to the slider's scale, and make other facelift to imitate a slider with "undefined default value"
+ $ticks.css("width", tick_ws);
+ var parent_offset = $(".surveys-slider").offset();
+ var ticks_offset = $slider.offset().left - parent_offset.left,
+ tick_cont_left = ticks_offset - tick_width/2;
+ $tick_container.css("left", tick_cont_left + "px");
+ $zerotick.css("width", tick_width);
+
+ _this.update_slider_state(0);
+
+ // put bounds' labels
+ $(".surveys-slider-bounds").css({
+ left: tick_cont_left + tick_width + "px",
+ width: $tick_container.width() - tick_width
+ });
+ $(".surveys-slider-left-bound").text(_this.get_label_by_index(1));//.css("left", tick_ws);
+ $(".surveys-slider-right-bound").text(_this.get_label_by_index(items_count - 1));
+
+ // handle hovering over ticks
+ $ticks.mouseenter(_this.on_enter).mouseleave(_this.on_leave);
+ $zerotick.mouseenter(_this.on_enter).mouseleave(_this.on_leave);
+
+ // handle clicks to ticks
+ $ticks.click(_this.on_click);
+ $zerotick.click(_this.on_click);
+
+ },
+
+ init_slider: function () {
+ var _this = surveys.slider,
+ items_count = _this.options.length;
+
+ _this.$slider = $("#slider"); // bind Slider control to div
+ _this.$slider.slider({
+ min: 0,
+ max: items_count - 1,
+ step: 1,
+ animate: "fast",
+ change: _this.on_slider_change,
+ slide: _this.on_slide,
+ stop: _this.on_slide_stop,
+ create: surveys.slider.build_slider // build the rest after slider is created
+ });
+ }
+ },
+
+ ui: {
+
+ is_optout_shown: false,
+ is_whatisthis_shown: false,
+
+ show_bottom_section: function () {
+ $(".bottom-section").show();
+ },
+
+ hide_bottom_section: function () {
+ var _this = surveys.ui;
+ $(".bottom-section").hide();
+ _this.is_optout_shown = false;
+ _this.is_whatisthis_shown = false;
+ },
+
+ hide_optout: function () {
+ surveys.ui.is_optout_shown = false;
+ $("#btab-optout").hide();
+ },
+
+ switch_tab: function (tab_name) {
+
+ $(".tabs").hide();
+ $("#tab-"+tab_name).show();
+ },
+
+ on_logo: function (e) {
+
+ },
+
+ on_close: function (e) {
+ var _this = surveys;
+ wot.ga.fire_event(wot.ga.categories.FBL, wot.ga.actions.FBL_closed, _this.target);
+
+ // give small time for GA to send event
+ surveys.report("close", {});
+ },
+
+ on_optout: function (e) {
+ var _this = surveys.ui,
+ $tab = $("#btab-optout");
+
+ $("#btab-whatsthis").hide(); // explicitly hide another bottom tabs
+ _this.is_whatisthis_shown = false;
+
+ if(_this.is_optout_shown) {
+ surveys.ui.hide_bottom_section();
+ _this.hide_optout();
+
+ } else {
+
+ surveys.ui.show_bottom_section();
+ $tab.show();
+
+ $(".button-yes", $tab).click(function () {
+ wot.ga.fire_event(wot.ga.categories.FBL, wot.ga.actions.FBL_optout_yes, _this.target);
+ surveys.report("optout", {});
+ });
+
+ $(".button-no", $tab).click(function () {
+ _this.hide_optout();
+ _this.hide_bottom_section();
+ wot.ga.fire_event(wot.ga.categories.FBL, wot.ga.actions.FBL_optout_no, _this.target);
+ });
+
+ _this.is_optout_shown = true;
+ wot.ga.fire_event(wot.ga.categories.FBL, wot.ga.actions.FBL_optout_shown, _this.target);
+ }
+ },
+
+ on_whatisthis: function (e) {
+
+ var _this = surveys.ui,
+ $btab = $("#btab-whatsthis");
+
+ _this.hide_optout();
+
+ if (_this.is_whatisthis_shown) {
+ surveys.ui.hide_bottom_section();
+ $btab.hide();
+ } else {
+ surveys.ui.show_bottom_section();
+ $btab.show();
+ _this.is_whatisthis_shown = true;
+ wot.ga.fire_event(wot.ga.categories.FBL, wot.ga.actions.FBL_whatisthis, _this.target);
+ }
+ },
+
+ on_submit: function (e) {
+ var _this = surveys;
+ if (_this.answer_value !== null) {
+
+ _this.report("submit", { answer: _this.answer_value });
+
+ wot.ga.fire_event(wot.ga.categories.FBL, wot.ga.actions.FBL_submit, _this.target);
+
+ _this.ui.hide_bottom_section();
+ _this.ui.switch_tab("final");
+ }
+ },
+
+ update_texts: function () {
+ var _this = surveys;
+
+ // sanitize the questions (to avoid XSS with addon) and replace placeholder %site% with target name
+ var text = wot.utils.htmlescape(_this.question.text).replace(/%site%/,
+ "<span class='domainname'>" + _this.decodedtarget + "</span>");
+
+ $(".surveys-question").html(text); // should be safe since we sanitized the question
+ },
+
+ update_submit_status: function () {
+ var $submit = $(".surveys-submit");
+
+ $submit.toggleClass("enabled", surveys.submit_enabled);
+ }
+
+ },
+
+ init: function () {
+ var _this = surveys;
+ var data = _this.extract_data(window.name); // we use name property to transfer data from addon's background
+ if (data) {
+ _this.decodedtarget = data.decodedtarget || "";
+ _this.target = data.target || "";
+ _this.question = data.question ? data.question : {};
+ _this.url = data.url;
+
+ _this.ui.update_texts();
+ _this.slider.prepare_values(_this.question.choices);
+
+ _this.slider.init_slider();
+ _this.report("shown", data);
+
+ // report after short delay to make sure GA code is inited
+ setTimeout(function () {
+ wot.ga.fire_event(wot.ga.categories.FBL, wot.ga.actions.FBL_shown, _this.target);
+ }, 500);
+
+ } else {
+ surveys.report("close", {});
+ }
+
+ // setup events' handlers
+ $(".surveys-submit").click(_this.ui.on_submit);
+ $(".surveys-optout").click(_this.ui.on_optout);
+ $(".close-button").click(_this.ui.on_close);
+ $(".surveys-whatsthis").click(_this.ui.on_whatisthis);
+ $(".wot-logo").click(_this.ui.on_logo);
+
+ $(".close-button-secondary").click(function (event) {
+ _this.ui.hide_bottom_section();
+ wot.ga.fire_event(wot.ga.categories.FBL, wot.ga.actions.FBL_bottom_close);
+ });
+
+ }
+};
+
+$(document).ready(function () {
+ surveys.init();
+});
diff --git a/content/injections/wot_proxy.js b/content/injections/wot_proxy.js
new file mode 100644
index 0000000..16d6e12
--- /dev/null
+++ b/content/injections/wot_proxy.js
@@ -0,0 +1,356 @@
+/*
+ wot.js
+ Copyright © 2012 - WOT Services Oy <info at mywot.com>
+
+ This file is part of WOT.
+
+ WOT 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 3 of the License, or
+ (at your option) any later version.
+
+ WOT 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 WOT. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* -- This is imitation of wot object which is in the Chrome version of the addon.
+* The reason why we use it like this, is to reuse same code without changes between browsers.
+* wot_proxy helps with it by providing the same interface of wot object but using config.js values.
+* */
+
+var wot = {
+ version: "20121116",
+ platform: "firefox",
+ debug: false, // when changing this, don't forget to switch ga_id value also!
+ default_component: 0,
+ enable_surveys: true, // Feedback loop engine
+
+ ga_id: "UA-35564069-1", // test: UA-35564069-1 , live: UA-2412412-8
+
+ // environment (browser, etc)
+ env: {
+ is_mailru: false,
+ is_yandex: false,
+ is_rambler: false
+ },
+
+ components: [
+ { name: 0 },
+ { name: 1 },
+ { name: 2 },
+ { name: 4 }
+ ],
+
+ reputationlevels: [
+ { name: "rx", min: -2 },
+ { name: "r0", min: -1 },
+ { name: "r1", min: 0 },
+ { name: "r2", min: 20 },
+ { name: "r3", min: 40 },
+ { name: "r4", min: 60 },
+ { name: "r5", min: 80 }
+ ],
+
+ confidencelevels: [
+ { name: "cx", min: -2 },
+ { name: "c0", min: -1 },
+ { name: "c1", min: 6 },
+ { name: "c2", min: 12 },
+ { name: "c3", min: 23 },
+ { name: "c4", min: 34 },
+ { name: "c5", min: 45 }
+ ],
+
+ searchtypes: {
+ optimized: 0,
+ worst: 1,
+ trustworthiness: 2
+ },
+
+ warningtypes: { /* bigger value = more severe warning */
+ none: 0,
+ notification: 1,
+ overlay: 2,
+ block: 3
+ },
+
+ warningreasons: { /* bigger value = more important reason */
+ none: 0,
+ unknown: 1,
+ rating: 2,
+ reputation: 3
+ },
+
+ urls: {
+ base: "http://www.mywot.com/",
+ scorecard: "http://www.mywot.com/scorecard/",
+ settings: "http://www.mywot.com/settings",
+ welcome: "http://www.mywot.com/settings/welcome",
+ setcookies: "http://www.mywot.com/setcookies.php",
+ update: "http://www.mywot.com/update",
+
+ contexts: {
+ rwlogo: "rw-logo",
+ rwsettings: "rw-settings",
+ rwguide: "rw-guide",
+ rwviewsc: "rw-viewsc",
+ rwprofile: "rw-profile",
+ rwmsg: "rw-msg",
+ warnviewsc: "warn-viewsc",
+ warnrate: "warn-rate",
+ popupviewsc: "popup",
+ popupdonuts: "popup-donuts"
+ }
+ },
+
+ firstrunupdate: 1, /* increase to show a page after an update */
+
+ cachestatus: {
+ error: 0,
+ ok: 1,
+ busy: 2,
+ retry: 3,
+ link: 4
+ },
+
+ expire_warned_after: 20000, // number of milliseconds after which warned flag will be expired
+
+ // Constants for playing with date & time (in seconds)
+ DT: {
+ MINUTE: 60,
+ HOUR: 3600,
+ DAY: 24 * 3600,
+ WEEK: 7 * 24 * 3600,
+ MONTH: 30 * 24 * 3600
+ },
+
+ /* logging */
+
+ log: function (s)
+ {
+ if (wot.debug) {
+ console.log(s);
+ }
+ },
+
+ /* events */
+
+ events: {},
+
+ bind: function (name, func, params)
+ {
+ },
+
+ post: function(name, message, data, port)
+ {
+ try {
+ //Call the function from sandbox and make sure data passing from here is safe
+ var data_obj = {
+ name: name,
+ message: message,
+ data: data
+ };
+
+ wot_post(JSON.stringify(data_obj));
+
+ } catch (e) {
+ console.log("Failed to call wot_post()" + e);
+ }
+ },
+
+ /* i18n */
+
+ /* helpers */
+
+ getuniques: function(list)
+ {
+ var seen = {};
+
+ return list.filter(function(item) {
+ if (seen[item]) {
+ return false;
+ } else {
+ seen[item] = true;
+ return true;
+ }
+ });
+ },
+
+ contextedurl: function(url, context)
+ {
+ var newurl = url;
+ newurl += ( (url.indexOf("?") > 0) ? "&" : "?" );
+ newurl += "utm_source=addon&utm_content=" + context;
+ return newurl;
+ },
+
+ detect_environment: function(readonly)
+ {
+ var readonly = readonly || false;
+ // try to understand in which environment we are run
+ var user_agent = window.navigator.userAgent || "";
+ wot.env.is_mailru = user_agent.indexOf("MRCHROME") >= 0;
+
+ if(wot.env.is_mailru) {
+ // set param to label requests
+ wot.partner = "mailru";
+ }
+
+ if(!readonly) wot.prefs.set("partner", wot.partner);
+ },
+
+ time_sincefirstrun: function()
+ {
+ // gives time (in seconds) spent from very first run of the addon.
+ var starttime_str = wot.prefs.get("firstrun:time");
+ if (starttime_str) {
+ var starttime = new Date(starttime_str);
+ return (new Date() - starttime) / 1000; // in seconds;
+
+ } else {
+ return undefined;
+ }
+ },
+
+ time_since: function(a, b) {
+
+ if (typeof a === "string") {
+ a = new Date(a);
+ }
+
+ b = b || new Date();
+
+ if (typeof b === "string") {
+ b = new Date(b);
+ }
+
+ return (b - a) / 1000; // in seconds
+ }
+};
+
+
+wot.utils = {
+
+ get_document: function (frame) {
+ frame = frame || window;
+ var framed_document = frame.document || frame.contentDocument;
+ return framed_document;
+ },
+
+ get_or_create_element: function (id, tag, frame) {
+ tag = tag || "div";
+ var framed_document = wot.utils.get_document(frame);
+
+ var elem = framed_document.getElementById(id);
+
+ if(!elem) {
+ elem = framed_document.createElement(tag);
+ elem.setAttribute("id", id);
+ }
+
+ return elem;
+ },
+
+ attach_element: function (element, frame) {
+ var framed_document = wot.utils.get_document(frame);
+
+ if(framed_document) {
+ var body = framed_document.getElementsByTagName("body");
+
+ if (!element || !body || !body.length) {
+ return false;
+ }
+
+ return body[0].appendChild(element);
+ } else {
+ wot.log("Can't get document of frame");
+ return false;
+ }
+
+ },
+
+ attach_style: function (style_file_or_object, uniq_id, frame) {
+ try {
+ uniq_id = uniq_id || null;
+ var reuse_style = false;
+
+ var framed_document = wot.utils.get_document(frame);
+
+ if(!framed_document) {
+ return false;
+ }
+
+ if(uniq_id) {
+ var el = framed_document.getElementById(uniq_id);
+ if(el) {
+ // if the element exists already - remove it to update styles
+ reuse_style = true;
+ }
+ }
+
+ var head = framed_document.getElementsByTagName("head");
+
+ if (!head || !head.length) {
+ return false;
+ }
+
+ var style = reuse_style ? el : framed_document.createElement("style");
+
+ if (!style) {
+ return false;
+ }
+
+ if(uniq_id) {
+ style.setAttribute("id", uniq_id);
+ }
+
+ style.setAttribute("type", "text/css");
+
+ if (typeof style_file_or_object === "object") {
+ style.innerText = style_file_or_object.style;
+ } else {
+ style.innerText = "@import \"chrome://wot/" + style_file_or_object + "\";";
+ }
+
+ if (!reuse_style) {
+ head[0].appendChild(style);
+ }
+
+ return true;
+ } catch (e) {
+ console.log("wot.utils.attach_style() failed with", e, "Arguments:", arguments);
+ return false;
+ }
+ },
+
+ processhtml: function (html, replaces) {
+ try {
+ replaces.forEach(function(item) {
+ html = html.replace(RegExp("{" + item.from + "}", "g"),
+ item.to);
+ });
+
+ return html;
+ } catch (e) {
+ console.log("warning.processhtml: failed with " + e);
+ }
+
+ return "";
+ },
+
+ htmlescape: function(str) {
+ var tagsToReplace = {
+ '&': '&',
+ '<': '<',
+ '>': '>'
+ };
+ return str.replace(/[&<>]/g, function(symb) {
+ return tagsToReplace[symb] || symb;
+ });
+ }
+};
diff --git a/content/overlay.xul b/content/overlay.xul
index 39c375a..0b147b4 100644
--- a/content/overlay.xul
+++ b/content/overlay.xul
@@ -34,6 +34,7 @@
<script type="application/x-javascript" src="chrome://wot/content/prefs.js"/>
<script type="application/x-javascript" src="chrome://wot/content/my.js"/>
<script type="application/x-javascript" src="chrome://wot/content/warning.js"/>
+ <script type="application/x-javascript" src="chrome://wot/content/surveys.js"/>
<script type="application/x-javascript" src="chrome://wot/content/update.js"/>
<script type="application/x-javascript" src="chrome://wot/content/popup.js"/>
<script type="application/x-javascript" src="chrome://wot/content/search.js"/>
diff --git a/content/search.js b/content/search.js
index c83f9a7..71d2670 100644
--- a/content/search.js
+++ b/content/search.js
@@ -1077,9 +1077,9 @@ var wot_search =
}
},
- getsandboxfunc: function(sandbox, name)
+ getsandboxfunc: function(sandbox, name, obj)
{
- var obj = this.sandboxapi;
+ obj = obj || wot_search.sandboxapi;
return function() {
var args = [ sandbox ];
@@ -1253,7 +1253,7 @@ var wot_search =
} catch (e) {
dump("wot_search.onclick: failed with " + e + "\n");
}
- },
+ }
};
wot_modules.push({ name: "wot_search", obj: wot_search });
diff --git a/content/surveys.js b/content/surveys.js
new file mode 100644
index 0000000..ef98e90
--- /dev/null
+++ b/content/surveys.js
@@ -0,0 +1,548 @@
+/*
+ warning.js
+ Copyright © 2012 - WOT Services Oy <info at mywot.com>
+
+ This file is part of WOT.
+
+ WOT 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 3 of the License, or
+ (at your option) any later version.
+
+ WOT 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 WOT. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+var wot_surveys = {
+
+ fbl_form_schema: "//",
+ storage_file: "storage.json",
+ fbl_form_uri: "fbl.local/feedback/surveys.html",
+ re_fbl_uri: null,
+ wrapper_id: "wot_surveys_wrapper",
+ is_shown: false,
+ wrapper: null,
+ pheight: 350,
+ pwidth: 392,
+ px: 10,
+ py: 10,
+ script_base: "resource://wot-base-dir/injections/",
+ scripts: [ "jquery.js", "jquery-ui-1.9.2.custom.js",
+ "wot_proxy.js", "ga_configure.js",
+ "surveys.widgets.js", "ga_init.js"],
+
+ asked: {},
+ asked_loaded: false,
+ last_time_asked: null,
+
+ calm_period: 1 * 24 * 3600, // Time in seconds after asking a question before we can ask next question
+ always_ask: ['api.mywot.com', 'fb.mywot.com'],
+ always_ask_passwd: "#surveymewot", // this string must be present to show survey by force
+ reset_passwd: "#wotresetsurveysettings", // this string must be present to reset timers and optout
+
+ FLAGS: {
+ none: 0, // a user didn't make any input yet
+ submited: 1, // a user has given the answer
+ closed: 2, // a user has closed the survey dialog without givin the answer
+ optedout: 3 // a user has clicked "Hide forever"
+ },
+
+ survey_url: function()
+ {
+ return this.fbl_form_schema + this.fbl_form_uri;
+ },
+
+ load_delayed: function ()
+ {
+ dump("WOT feedback Loop is being initialized\n");
+ this.re_fbl_uri = new RegExp("^" + wot_surveys.fbl_form_uri, "i"); // prepare RegExp once to use often
+
+ try {
+ var lasttime = wot_prefs.getChar("feedback_lasttimeasked", null);
+ if(lasttime) {
+ this.last_time_asked = new Date(lasttime);
+ }
+ } catch (e) {
+ this.last_time_asked = null;
+ dump("wot_surveys.load_delayed raised the exeption:" + e + "\n");
+ }
+
+ dump("WOT FBL: last_time_asked = " + this.last_time_asked + "\n");
+
+ // Load the JSON stored data about asked websites
+ wot_file.read_json(wot_surveys.storage_file, function (data, status) {
+ dump("File is loaded? " + JSON.stringify(data) + "\n");
+
+ if (data && data.asked) {
+ wot_surveys.asked = data.asked;
+ }
+
+ wot_surveys.asked_loaded = true; // set this flag anyway to indicate that loading finished
+ });
+ },
+
+ domcontentloaded: function(event)
+ {
+ try {
+
+ if (!event || !wot_util.isenabled()) {
+ return;
+ }
+
+ var content = event.originalTarget,
+ location = (content && content.location) ? content.location : {};
+
+ var is_framed = (content.defaultView != content.defaultView.top);
+
+ // Process framed documents differently than normal ones
+ if (is_framed) {
+
+ if (location) {
+ // skip all frames except of our own FBL form
+ if (wot_surveys.re_fbl_uri.test(location.host + location.pathname)) {
+ // here we found WOT FBL form loaded into a frame. Next step - to inject JS into it.
+ wot_surveys.inject_javascript(content);
+ }
+ }
+
+ } else {
+
+ // same code as for warning screen
+ if (!content || !location || !location.href ||
+ wot_url.isprivate(location.href) || !(/^https?:$/.test(location.protocol))) {
+ return;
+ }
+
+ var hostname = wot_url.gethostname(location.href);
+ var warning_type = wot_warning.isdangerous(hostname, false);
+
+ // ask only if no big Warning is going to be shown
+ if (warning_type == WOT_WARNING_NONE|| warning_type == WOT_WARNING_NOTIFICATION) {
+ wot_surveys.try_show(content, hostname);
+ }
+ }
+
+ } catch (e) {
+ dump("wot_surveys.domcontentloaded: failed with " + e + "\n");
+ }
+
+ },
+
+ unload: function (event)
+ {
+ // TODO: Implement some cleaning here?
+ },
+
+ get_or_create_sandbox: function(content)
+ {
+ var sandbox = content.wotsandbox;
+
+ if (!sandbox) {
+ var wnd = new XPCNativeWrapper(content.defaultView);
+ sandbox = new Components.utils.Sandbox(wnd);
+
+ sandbox.window = wnd;
+ sandbox.document = sandbox.window.document;
+ sandbox.__proto__ = sandbox.window;
+
+ sandbox.wot_post = wot_search.getsandboxfunc(sandbox, "wot_post", wot_surveys.sandboxapi);
+
+ content.wotsandbox = sandbox;
+ }
+
+ return sandbox;
+ },
+
+ load_file: function(file)
+ {
+ var str = "";
+
+ try {
+ var ioService = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+ var scriptableStream = Components
+ .classes["@mozilla.org/scriptableinputstream;1"]
+ .getService(Components.interfaces.nsIScriptableInputStream);
+
+ var channel = ioService.newChannel(file, null, null);
+ var input = channel.open();
+ scriptableStream.init(input);
+ str = scriptableStream.read(input.available());
+ scriptableStream.close();
+ input.close();
+ } catch (e) {
+ dump("wot_surveys.load_file(): failed with " + e + "\n");
+ }
+
+ return str;
+ },
+
+ inject_javascript: function (content)
+ {
+ dump("Going to inject JS into FBL form\n");
+ var sandbox = wot_surveys.get_or_create_sandbox(content);
+
+ var contents = "",
+ url = "";
+
+ // load all scripts and join to one text
+ for(var i=0; i < wot_surveys.scripts.length; i++) {
+ url = wot_surveys.script_base + wot_surveys.scripts[i];
+ contents = wot_surveys.load_file(url);
+
+ // run scripts in fbl form-page
+ try {
+ Components.utils.evalInSandbox(contents, sandbox);
+ } catch (e) {
+ dump("wot_surveys.load_script(): evalInSandbox " +
+ "failed with " + e + "\n");
+ }
+ }
+
+ },
+
+ inject: function (doc, question)
+ {
+ var ws = wot_surveys;
+ var location = doc.defaultView.location;
+
+ // skip params and hash in the URL
+ question.url = location.protocol + "//" + location.host + location.pathname;
+
+ var wrapper = doc.getElementById(ws.wrapper_id);
+ if(wrapper) {
+ return;
+ }
+ wrapper = doc.createElement("iframe");
+ wrapper.setAttribute("id", ws.wrapper_id);
+
+ if (!wrapper) {
+ dump("can't add element to DOM / wot.surveys.inject_placeholder()");
+ return;
+ }
+
+ ws.wrapper = wrapper; // keep the link to the element to destroy it
+
+ wrapper.setAttribute("scrolling", "no");
+
+ wrapper.setAttribute("style",
+ "position: fixed; " +
+ "top: " + ws.py + "px; " +
+ "left: "+ ws.px +"px;" +
+ "width: "+ ws.pwidth +"px; " +
+ "height: "+ ws.pheight +"px; " +
+ "z-index: 2147483647; " +
+ "border: none; visibility: hidden;");
+
+ wrapper.setAttribute("src", this.survey_url());
+
+ var encoded_data = btoa(JSON.stringify(question));
+
+ // Probably in FF we should transfer data to the frame by injecting it as JS (json) object instead of
+ // relying to "name" property
+ wrapper.setAttribute("name", encoded_data); // transfer question's data via "name" property of iframe
+
+ wot_browser.attach_element(wrapper, doc.defaultView); // attach iframe wrapper to DOM
+ },
+
+ try_show: function (doc, hostname)
+ {
+ try {
+ var url = doc.defaultView.location.href;
+
+ // test url for RESET command
+ if (wot_surveys.always_ask.indexOf(hostname) >= 0 && url && url.indexOf(wot_surveys.reset_passwd) >= 0) {
+ wot_surveys.reset_settings(hostname);
+ return;
+ }
+
+ var question = wot_surveys.get_question(hostname);
+
+ if (this.is_tts(hostname, url, question.question)) {
+ this.inject(doc, question);
+ }
+
+ } catch (e) {
+ dump("wot_surveys.try_show() failed with " + e + "\n");
+ }
+ },
+
+ reset_settings: function (hostname)
+ {
+ var ws = wot_surveys;
+ ws.asked_loaded = true;
+ ws.last_time_asked =null;
+ ws.asked = {}; // reset the list of websites asked about
+ ws.opt_out(false); // reset opt-out
+ wot_prefs.setChar("feedback_lasttimeasked", ""); // reset time
+ ws.remember_asked(hostname, 0, ws.FLAGS.none);
+ },
+
+ remove_form: function (sandbox, timeout)
+ {
+ try {
+
+ timeout = timeout || 100;
+
+ window.setTimeout(function () {
+ var wrapper = wot_surveys.get_wrapper(sandbox);
+ if (wrapper) {
+ wrapper.parentNode.removeChild(wrapper);
+ }
+ }, timeout);
+
+ } catch (e) {
+ dump("wot_surveys.remove_form() failed with " + e + "\n");
+ }
+ },
+
+ get_question: function (hostname)
+ {
+ try {
+ var question_id = wot_cache.get(hostname, "question_id");
+ var question_text = wot_cache.get(hostname, "question_text");
+ var choices_number = wot_cache.get(hostname, "choices_number");
+
+ dump("SHOW: id, text : " + String(question_id) + " , " + String(question_text) + "\n");
+
+ if (choices_number > 0) {
+ var question = {
+ target: hostname,
+ decodedtarget: wot_idn.idntoutf(hostname),
+ question: {
+ id: question_id,
+ text: question_text,
+ choices: []
+ }
+ };
+
+ for(var i= 0, v, t; i < choices_number; i++) {
+ v = wot_cache.get(hostname, "choice_value_" + i);
+ t = wot_cache.get(hostname, "choice_text_" + i);
+ question.question.choices.push({ value: v, text: t });
+ }
+
+ return question;
+
+ } else {
+ return {};
+ }
+
+ } catch (e) {
+ return {};
+ }
+
+ },
+
+ is_tts: function (hostname, url, question)
+ {
+ var ws = wot_surveys;
+
+ dump("IS_TTS? " + JSON.stringify(question) + "\n");
+
+ try {
+ if(!wot_surveys.asked_loaded) return false; // data isn't ready for process
+ dump("if(!wot_surveys.asked_loaded) passed.\n");
+
+ if(!(question && question.id !== undefined && question.text && question.choices)) {
+ // no question was given for the current website - do nothing
+ return false;
+ }
+ dump("is_tts: question test passed.\n");
+
+ // on special domains we should always show the survey if there is a special password given (for testing purposes)
+ // e.g. try this url http://api.mywot.com/test.html#surveymewot
+ if (ws.always_ask.indexOf(hostname) >= 0 && url && url.indexOf(ws.always_ask_passwd) >= 0) {
+ return true;
+ }
+ dump("is_tts: always ask test not passed.\n");
+
+ if (ws.is_optedout() || !wot_prefs.getBool("feedback_enabled", true)) {
+ return false;
+ }
+ dump("is_tts: opt-out and feedback_enabled test passed.\n");
+
+ // check if have asked the user more than X days ago or never before
+ if (ws.last_time_asked && wot_util.time_since(ws.last_time_asked) < ws.calm_period) {
+ return false;
+ }
+ dump("is_tts: last-time test passed.\n");
+
+ // check whether we already have asked the user about current website
+ if (ws.asked[hostname] && ws.asked[hostname][question.id]) {
+ // here we could test also if user just closed the survey last time without providing any info
+ // (in case if we want to be more annoying)
+ return false;
+ }
+ dump("is_tts: already asked test passed -> show it!\n");
+
+ return true;
+ } catch (e) {
+ dump("wot_surveys.is_tts() failed with " + e + "\n");
+ return false;
+ }
+
+ },
+
+ is_optedout: function()
+ {
+ return wot_prefs.getBool("feedback_optedout", false);
+ },
+
+ opt_out: function(value)
+ {
+ value = (value === undefined) ? true : value;
+ wot_prefs.setBool("feedback_optedout", value);
+ },
+
+ remember_asked: function(target, question_id, status) {
+ var ws = wot_surveys;
+
+ try {
+
+ status = status === undefined ? ws.FLAGS.none : status;
+
+ var asked_data = {
+ time: new Date(), // time of first show the survey
+ status: status
+ };
+
+ if (ws.asked[target]) {
+ if (ws.asked[target][question_id]) {
+ asked_data = ws.asked[target][question_id];
+ asked_data.status = status; // just update the status
+ } else {
+ ws.asked[target][question_id] = {};
+ }
+
+ } else {
+ ws.asked[target] = {};
+ ws.asked[target][question_id] = {};
+ }
+
+ ws.asked[target][question_id] = asked_data; // keep in runtime variable
+
+ var storage = {
+ asked: wot_surveys.asked
+ };
+ wot_file.save_json(wot_surveys.storage_file, storage); // and dump to file
+
+ } catch (e) {
+ console.error("remember_asked() failed with", e);
+ }
+ },
+
+ save_asked_status: function (data, status) {
+ var ws = wot_surveys;
+ try {
+ if (data && data.target && data.question_id) {
+ ws.remember_asked(data.target, data.question_id, status);
+
+ // we remember the last time of user's interaction with FBL
+ ws.last_time_asked = new Date();
+ wot_prefs.setChar("feedback_lasttimeasked", ws.last_time_asked);
+ }
+ } catch (e) {
+ console.error(e);
+ }
+ },
+
+ get_top_content: function (sandbox)
+ {
+ var top = null;
+ if(sandbox && sandbox.window && sandbox.window.top) {
+ top = sandbox.window.top; // look into top content window document
+ }
+ return top;
+ },
+
+ get_wrapper: function (sandbox)
+ {
+ var wrapper = null;
+
+ try {
+ var top = wot_surveys.get_top_content(sandbox);
+ if(top && top.document) {
+ wrapper = top.document.getElementById(wot_surveys.wrapper_id);
+ if(!wrapper) {
+ dump("wot_surveys.get_wrapper(): can't find FBL wrapper in the document\n");
+ }
+ }
+ } catch (e) {
+ dump("wot_surveys.get_wrapper() failed with " + e + "\n");
+ }
+
+ return wrapper;
+ },
+
+ reveal_form: function (sandbox)
+ {
+ var wrapper = wot_surveys.get_wrapper(sandbox);
+
+ if (wrapper) {
+ var style = wrapper.getAttribute("style") || "";
+ if (style) {
+ style = style.replace(/^(.*visibility: )(hidden;)(.*)$/, "$1visible;$3"); // replace hidden -> visible
+ wrapper.setAttribute("style", style);
+ }
+ }
+ },
+
+ dispatch: function (message, data, sandbox)
+ {
+ switch(message) {
+ case "shown": // FBL was shown
+ wot_surveys.reveal_form(sandbox); // make iframe visible
+ wot_surveys.save_asked_status(data, wot_surveys.FLAGS.none);
+ break;
+ case "close": // FBL is asking to close it
+ // data.target
+ wot_surveys.save_asked_status(data, wot_surveys.FLAGS.closed);
+ wot_surveys.remove_form(sandbox);
+ break;
+ case "optout": // FBL says the user wants to opt-out from the feedback loop.
+ // data.target
+ wot_surveys.opt_out(); // store setting
+ wot_surveys.save_asked_status(data, wot_surveys.FLAGS.optedout);
+ wot_surveys.remove_form(sandbox);
+ break;
+ case "submit":
+ // data.target, .url, .question_id, .answer
+ wot_api_feedback.send(data.url, data.question_id, data.answer);
+ wot_surveys.save_asked_status(data, wot_surveys.FLAGS.submited);
+ wot_surveys.remove_form(sandbox, 1500); // wait a bit to show "thank you!"
+ break;
+ }
+ },
+
+ // This is a wrapper around functions that might be called from the injected JS
+ sandboxapi: {
+
+ wot_post: function (sandbox, data_json) {
+ // this func is called from /content/injections/wot_proxy.js : wot.post()
+
+ try {
+ // try un-json data (DON'T call any methods of data_json since it is unsafe!)
+ var data = JSON.parse(data_json);
+
+ dump("wot_surveys.sandpoxapi.wot_post(): " + JSON.stringify(data) + "\n");
+
+ if (data && data.message && data.data) {
+ wot_surveys.dispatch(data.message, data.data, sandbox);
+ }
+ } catch (e) {
+ dump("wot_surveys.sandboxapi.wot_post(): failed with " + e + "\n");
+ }
+
+ }
+
+ }
+
+};
+
+wot_modules.push({ name: "wot_surveys", obj: wot_surveys });
diff --git a/content/ui.js b/content/ui.js
index fc91a43..772d2fb 100644
--- a/content/ui.js
+++ b/content/ui.js
@@ -112,7 +112,11 @@ var wot_status = {
if (type == WOT_WARNING_NOTIFICATION || type == WOT_WARNING_DOM) {
wot_warning.add(wot_core.hostname, content, type);
} else {
- if(type != WOT_WARNING_BLOCK) wot_warning.hide(content);
+ if(type != WOT_WARNING_BLOCK) {
+ wot_warning.hide(content);
+ // TODO: here we should put a call for FBL showing
+// wot_surveys.inject();
+ }
}
} catch (e) {
dump("wot_status.update: failed with " + e + "\n");
diff --git a/content/util.js b/content/util.js
index 5b80007..d5a2bf3 100644
--- a/content/util.js
+++ b/content/util.js
@@ -20,6 +20,12 @@
var wot_util =
{
+ reportError: function(params)
+ {
+ Components.utils.reportError(JSON.stringify(arguments));
+
+ },
+
isenabled: function()
{
try {
@@ -50,6 +56,40 @@ var wot_util =
}
return null;
+ },
+
+ time_sincefirstrun: function()
+ {
+ try {
+ // gives time (in seconds) spent from very first run of the addon.
+ var starttime_str = wot_prefs.getChar("firstrun_time");
+ if (starttime_str) {
+ var starttime = new Date(starttime_str);
+ return (new Date() - starttime) / 1000; // in seconds;
+ } else {
+ return undefined;
+ }
+ } catch (e) {
+ return undefined;
+ }
+ },
+
+ time_since: function(a, b) {
+ try {
+ if (typeof a === "string") {
+ a = new Date(a);
+ }
+
+ b = b || new Date();
+
+ if (typeof b === "string") {
+ b = new Date(b);
+ }
+
+ return (b - a) / 1000; // in seconds
+ } catch (e) {
+ return null;
+ }
}
};
@@ -438,6 +478,62 @@ var wot_browser =
} catch (e) {
dump("wot_browser.installsearch: failed with " + e + "\n");
}
+ },
+
+ get_document: function (frame)
+ {
+ try {
+ frame = frame || getBrowser();
+ var framed_document = frame.document || frame.contentDocument;
+ return framed_document;
+ } catch (e) {
+ dump("wot_browser.get_document failed with " + e + "\n");
+ return null;
+ }
+ },
+
+ get_or_create_element: function (id, tag, frame)
+ {
+ try {
+ tag = tag || "div";
+ var framed_document = this.get_document(frame);
+
+ var elem = framed_document.getElementById(id);
+
+ if(!elem) {
+ elem = framed_document.createElement(tag);
+ elem.setAttribute("id", id);
+ }
+
+ return elem;
+ } catch (e) {
+ dump("wot_browser.get_or_create_element failed with " + e + "\n");
+ return null;
+ }
+ },
+
+ attach_element: function (element, frame)
+ {
+ try {
+ var framed_document = this.get_document(frame);
+
+ if(framed_document) {
+ var body = framed_document.getElementsByTagName("body");
+
+ if (!element || !body || !body.length) {
+ return false;
+ }
+
+ return body[0].appendChild(element);
+ } else {
+ dump("Can't get document of frame");
+ return false;
+ }
+
+ } catch (e) {
+ dump("wot_browser.attach_element failed with " + e + "\n");
+ return null;
+ }
}
};
@@ -930,3 +1026,100 @@ var wot_css =
}
}
};
+
+wot_file = {
+
+ wot_dir: "WOT",
+
+ import_libs: function()
+ {
+ Components.utils.import("resource://gre/modules/NetUtil.jsm");
+ Components.utils.import("resource://gre/modules/FileUtils.jsm");
+ },
+
+ read_json: function (filename, callback) {
+
+ try {
+
+ wot_file.import_libs();
+
+ var dir = FileUtils.getDir("ProfD", [wot_file.wot_dir], true); // to make sure the Dir exists
+ var file = FileUtils.getFile("ProfD", [wot_file.wot_dir, filename]);
+
+ NetUtil.asyncFetch(file, function(inputStream, status) {
+
+ if (!Components.isSuccessCode(status)) {
+ // Handle error!
+ callback({});
+ return;
+ }
+
+ try {
+ var data = NetUtil.readInputStreamToString(inputStream, inputStream.available());
+
+ if (data) {
+ var res = JSON.parse(data);
+ if (res instanceof Object) {
+ callback(res);
+ }
+ }
+
+ } catch (e) {
+ dump("utils.wot_file.read_json() is failed with " + e + "\n");
+ callback({}); // anyway, provide empty object
+ return;
+ }
+
+ });
+
+ } catch (e) {
+ dump("wot_file.read_json() failed with " + e + "\n");
+ callback({}); // anyway, provide empty object
+ }
+
+ },
+
+ save_json: function (filename, obj, callback) {
+
+ callback = callback || function(status){};
+
+ try {
+ wot_file.import_libs();
+
+ var dir = FileUtils.getDir("ProfD", [wot_file.wot_dir], true); // to make sure the Dir exists
+ var file = FileUtils.getFile("ProfD", [wot_file.wot_dir, filename]);
+
+ // You can also optionally pass a flags parameter here. It defaults to
+ // FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_TRUNCATE;
+ var ostream = FileUtils.openSafeFileOutputStream(file);
+
+ var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].
+ createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
+ converter.charset = "UTF-8";
+
+ var data = JSON.stringify(obj);
+ var istream = converter.convertToInputStream(data);
+
+ NetUtil.asyncCopy(istream, ostream, function(status) {
+ if (!Components.isSuccessCode(status)) {
+ // Handle error!
+ callback(false);
+ return;
+ }
+
+ // Data has been written to the file.
+ callback(true);
+ });
+
+ } catch (e) {
+ dump("wot_file.save_json() failed with " + e + "\n");
+ callback(false); // report about failed attempt to save
+ }
+ },
+
+ remove: function (filename, callback)
+ {
+ // TODO: implement a function which will delete the file (for the case of uninstalling the addon)
+ }
+
+};
diff --git a/extra/feedback/assets/close-button-secondary.png b/extra/feedback/assets/close-button-secondary.png
new file mode 100644
index 0000000..f61b7e2
Binary files /dev/null and b/extra/feedback/assets/close-button-secondary.png differ
diff --git a/extra/feedback/assets/close-button.png b/extra/feedback/assets/close-button.png
new file mode 100644
index 0000000..1f869fa
Binary files /dev/null and b/extra/feedback/assets/close-button.png differ
diff --git a/extra/feedback/assets/logo.png b/extra/feedback/assets/logo.png
new file mode 100644
index 0000000..aba32ed
Binary files /dev/null and b/extra/feedback/assets/logo.png differ
diff --git a/extra/feedback/assets/slide-sep.png b/extra/feedback/assets/slide-sep.png
new file mode 100644
index 0000000..d728fa1
Binary files /dev/null and b/extra/feedback/assets/slide-sep.png differ
diff --git a/extra/feedback/assets/slider-handle.png b/extra/feedback/assets/slider-handle.png
new file mode 100644
index 0000000..0d44f45
Binary files /dev/null and b/extra/feedback/assets/slider-handle.png differ
diff --git a/extra/feedback/assets/submit-button.png b/extra/feedback/assets/submit-button.png
new file mode 100644
index 0000000..c32583d
Binary files /dev/null and b/extra/feedback/assets/submit-button.png differ
diff --git a/extra/feedback/surveys.html b/extra/feedback/surveys.html
new file mode 100644
index 0000000..64515b9
--- /dev/null
+++ b/extra/feedback/surveys.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Web Of Trust Feedback Loop</title>
+ <link rel="stylesheet" href="/feedback/surveys.widgets.css" type="text/css"/>
+ </head>
+ <body>
+ <div class="surveys-window">
+ <div class="top-section">
+
+ <div id="tab-question" class="tabs">
+ <div class="wot-logo"></div>
+ <div class="close-button"></div>
+ <div class="surveys-question"></div>
+ <div class="surveys-answer">
+ <div class="surveys-slider">
+ <div class="surveys-slider-bounds">
+ <div class="surveys-slider-left-bound"></div>
+ <div class="surveys-slider-right-bound"></div>
+ </div>
+ <div class="ticks-container">
+ <div class="zero-tick">
+ <div class="zero-tick-mark"></div>
+ </div>
+ </div>
+ <div id="slider"></div>
+ </div>
+ <div class="surveys-slider-label-wrapper">
+ <div class="surveys-slider-label"></div>
+ </div>
+ </div>
+ <div class="surveys-submit">Submit</div>
+ <div class="surveys-optout">
+ <span class="pseudo-link">Hide forever</span>
+ </div>
+ <div class="surveys-whatsthis">
+ <span class="pseudo-link">What's this?</span>
+ </div>
+ </div>
+
+ <div id="tab-final" class="tabs">
+ <div class="wot-logo"></div>
+ <div class="thank-you-text">Thank you!</div>
+ </div>
+
+ </div>
+
+ <div class="bottom-section">
+ <div class="close-button-secondary"></div>
+
+ <div id="btab-optout">
+ <div class="text-optout-confirm">
+ If you prefer not to be asked questions, click "yes".
+ </div>
+ <div class="optout-buttons">
+ <div class="button-yes">yes</div>
+ <div class="button-no">No</div>
+ </div>
+ </div>
+
+ <div id="btab-whatsthis">
+ <a href="http://www.mywot.com?utm_source=addon&utm_content=whatisthis" target="_blank">Web of Trust (WOT)</a> is working to promote safety and quality across the web for millions of users.
+ You can help by submitting your opinions whenever you see this prompt.
+ </div>
+
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/extra/feedback/surveys.widgets.css b/extra/feedback/surveys.widgets.css
new file mode 100644
index 0000000..0ba7221
--- /dev/null
+++ b/extra/feedback/surveys.widgets.css
@@ -0,0 +1,390 @@
+body {
+ margin: 0;
+ padding: 0;
+ -webkit-user-select: none;
+}
+
+.surveys-window {
+ position: relative;
+ margin: 10px;
+ border: 1px solid silver;
+ font-family: arial, sans-serif;
+ font-size: 11pt;
+ box-shadow: 0px 0px 10px rgba(27, 27, 27, 0.8);
+}
+
+.top-section {
+ width: 340px;
+ padding: 20px 15px;
+ border-bottom: 1px solid silver;
+ background: #ffffff;
+}
+
+.bottom-section {
+ display: none;
+ padding: 1em 1em 1.2em;
+ background-color: #edf0f5;
+ box-shadow: inset 0 1px 1px rgba(152, 157, 169, 0.7);
+ font-size: 9pt; /* diff than Chrome */
+ color: #656565;
+ position: relative;
+}
+
+/* Top Section */
+
+#tab-question {
+ display: block;
+}
+
+#tab-final {
+ display: none;
+ font-size: 110%;
+}
+
+.wot-logo {
+ position: absolute;
+ top: 10px;
+ left: 10px;
+ width: 42px;
+ height: 17px;
+ background-image: url(/feedback/assets/logo.png);
+ background-repeat: no-repeat;
+}
+
+.close-button {
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ width: 19px;
+ height: 19px;
+ background-image: url(/feedback/assets/close-button.png);
+ background-position: 0 0;
+ cursor: pointer;
+}
+
+.close-button:hover {
+ background-position: 0 -19px;
+}
+
+.surveys-question {
+ margin: 1em 0em 1em 0;
+ font-size: 13pt;
+ text-align: center;
+ -webkit-user-select: text;
+}
+
+.surveys-question .domainname {
+ font-weight: bold;
+}
+
+.surveys-answer {
+ margin: 2.5em auto 0em;
+}
+
+.surveys-slider {
+ position: relative;
+}
+
+.slider-tick {
+ width: 5px;
+ height: 13px;
+ position: relative;
+ top: 0;
+ left: 0;
+ border-right: 1px solid silver;
+ float: left;
+ background-image: -moz-linear-gradient(90deg, #d8dde5 0%, #e4e9f1 38%, #bbc0cb 100%);
+ cursor: pointer;
+}
+
+.slider-tick.selected {
+ background-image: -moz-linear-gradient(90deg, #70a8ba 0%, #a3d7ec 38%, #70a8ba 100%);
+ border-color: #70a8ba;
+}
+
+.slider-tick.hovered {
+ background-image: -moz-linear-gradient(90deg, #70a8ba 0%, #a3d7ec 38%, #70a8ba 100%);
+}
+
+.zero-tick {
+ position: relative;
+ float: left;
+}
+
+.zero-tick-mark {
+ width:4px;
+ height: 4px;
+ margin: 4px auto;
+ border: 1px solid #c6cbd6;
+ border-radius: 5px;
+ background-image: -moz-linear-gradient(90deg, #d8dde5 0%, #c6cbd6 100%);
+}
+
+.zero-tick.selected .zero-tick-mark {
+ background-image: -moz-linear-gradient(90deg, #70a8ba 0%, #a3d7ec 38%, #70a8ba 100%);
+}
+
+.zero-tick.hovered .zero-tick-mark {
+ background-image: -moz-linear-gradient(90deg, #70a8ba 0%, #a3d7ec 38%, #70a8ba 100%);
+}
+
+.slider-tick:nth-child(2) {
+ border-top-left-radius: 8px;
+ border-bottom-left-radius: 8px;
+}
+
+.slider-tick:last-child {
+ border-top-right-radius: 8px;
+ border-bottom-right-radius: 8px;
+}
+
+.ticks-container {
+ /*border: 1px solid silver;*/
+ /*border-radius: 2px;*/
+ position: absolute;
+ height: 13px;
+ margin-bottom: 6px;
+}
+
+#slider {
+ height: 2px;
+ max-width: 80%;
+ top: 18px;
+ margin: auto;
+ border: none;
+ background-color: #c5cbd9;
+ box-shadow: 0 0 2px #7f8693;
+ cursor: pointer;
+}
+
+.surveys-slider-label-wrapper {
+ height: 1.5em;
+ margin: 30px auto auto;
+ text-align: center;
+}
+
+.surveys-slider-label {
+ text-align: center;
+ color: #bebebe;
+ display: inline-block;
+ font-size: 90%;
+}
+
+.surveys-slider-label.selected {
+ color: #444444;
+}
+
+.surveys-slider-label:before,
+.surveys-slider-label:after {
+ position: relative;
+ width: 7px;
+ height: 3px;
+ content: "";
+ display: block;
+ top: 0.7em;
+}
+
+.surveys-slider-label.isset:before,
+.surveys-slider-label.isset:after {
+ background-image: -moz-linear-gradient(90deg, #70a8ba 0%, #a3d7ec 38%, #70a8ba 100%);
+}
+
+.surveys-slider-label:before {
+ padding-left: 10px;
+ left: -1.9em; /* diff than Chrome */
+}
+
+.surveys-slider-label:after {
+ padding-right: 10px;
+ left: 0.6em; /* diff than Chrome */
+ float: right;
+ top: 0.5em; /* diff than Chrome */
+}
+
+.surveys-submit {
+ width: 101px;
+ height: 30px;
+ margin: 0.5em auto 0.1em;
+ padding-top: 0.6em;
+ padding-bottom: 0.1em; /* :active shift compensator */
+ text-align: center;
+ background-image: url(/feedback/assets/submit-button.png);
+ background-position: 0 -111px;
+ font-size: 92%;
+ color: #888;
+ text-shadow: 1px 1px 2px silver;
+}
+
+.surveys-submit.enabled {
+ background-position: 0 0px;
+ color: #3E4B3E;
+ cursor: pointer;
+}
+
+.surveys-submit.enabled:hover {
+ background-position: 0 -37px;
+}
+
+.surveys-submit.enabled:active {
+ padding-top: 0.7em;
+ padding-bottom: 0;
+ background-position: 0 -74px;
+}
+
+.surveys-whatsthis,
+.surveys-optout {
+ color: #a7a7a7;
+ font-size: 80%;
+}
+
+.surveys-whatsthis {
+ float: right;
+ top: -1em;
+ position: relative;
+}
+
+.pseudo-link {
+ border-bottom: 1px dashed silver;
+ cursor: pointer;
+}
+
+.pseudo-link:hover {
+ border-bottom: 1px dashed silver;
+ color: #5d5d5d;
+}
+
+/* From jquery.ui.slider.css */
+.ui-slider { position: relative; text-align: left; }
+.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; }
+.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; }
+
+.ui-slider-horizontal { height: .8em; }
+.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; }
+.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; }
+.ui-slider-horizontal .ui-slider-range-min { left: 0; }
+.ui-slider-horizontal .ui-slider-range-max { right: 0; }
+
+/* overriding styles of Jquery UI components */
+.ui-widget-content,
+.ui-state-default,
+.ui-widget-content .ui-state-default {
+ background-image: none;
+ border: none;
+ background-color: transparent;
+}
+
+
+#slider .ui-slider-handle {
+ width: 26px;
+ height: 29px;
+ background-image: url(/feedback/assets/slider-handle.png);
+ background-repeat: no-repeat;
+ top: -11px;
+ margin-left: -13px;
+}
+
+#slider .ui-slider-handle.ui-state-focus {
+ border: none;
+ outline: none;
+}
+
+.surveys-slider-bounds {
+ position: relative;
+}
+
+.surveys-slider-right-bound,
+.surveys-slider-left-bound {
+ position: absolute;
+ top: -1.9em;
+ font-size: 80%;
+ color: gray;
+}
+
+.surveys-slider-left-bound {
+ left: 4px;
+}
+
+.surveys-slider-right-bound {
+ right: 4px;
+}
+
+/* Tab Final */
+
+.thank-you-text {
+ text-align: center;
+}
+
+/* Bottom Section -- */
+
+.close-button-secondary {
+ width: 11px;
+ height: 11px;
+ position: absolute;
+ right: 10px;
+ top: 10px;
+ background-image: url(/feedback/assets/close-button-secondary.png);
+ background-position: 0 0;
+}
+
+.close-button-secondary:hover {
+ background-position: 0 -12px;
+}
+
+/* -- tab OptOut */
+
+#btab-optout {
+ display: none;
+}
+
+.text-optout-confirm {
+ display: inline-block;
+ max-width: 210px;
+ float: left;
+ padding-right: 1em; /* diff than Chrome */
+
+}
+
+.optout-buttons {
+ display: inline-block;
+}
+
+.button-yes,
+.button-no {
+ border: 1px solid silver;
+ display: inline-block;
+ padding: 0.3em 1em;
+ text-align: center;
+ border-radius: 2px;
+ cursor: pointer;
+ box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.47);
+}
+
+.button-yes:hover,
+.button-no:hover {
+ background-image: -moz-linear-gradient(top, #e5fad1 0%, #b2e186 39%, #83bc4a 83%, #83BC4A);
+}
+
+.button-yes {
+ margin-right: 1em;
+ background-color: silver;
+ color: #787f8c;
+ border-color: #b5bcca;
+ background-image: -moz-linear-gradient(top, #edf1f8 0%, #e7ebf5 6%, #d9dee9 48%, #b3b9c7);
+ box-shadow: 0px 1px 1px #787f8c;
+}
+
+.button-no {
+ border-color: #84bf42;
+ background-image: -moz-linear-gradient(top, #ade279 0%, #67a62b);
+}
+
+#btab-whatsthis {
+ display: none;
+ padding-top: 0.5em;
+}
+
+#btab-whatsthis a,
+#btab-whatsthis a:visited,
+#btab-whatsthis a:link {
+ color: #4848F1;
+}
diff --git a/extra/feedback/surveys_test.html b/extra/feedback/surveys_test.html
new file mode 100644
index 0000000..f19d037
--- /dev/null
+++ b/extra/feedback/surveys_test.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>WOT Surveys page test</title>
+</head>
+<body style="background-color: #f5f5f5; background-image: url('http://st.gdefon.ru/wallpapers_original/wallpapers/74533_cvety_foto_oboi_2560x1600_(www.GdeFon.ru).jpg')">
+ <script src="surveys_test.js"></script>
+</body>
+</html>
diff --git a/extra/feedback/surveys_test.js b/extra/feedback/surveys_test.js
new file mode 100644
index 0000000..8684079
--- /dev/null
+++ b/extra/feedback/surveys_test.js
@@ -0,0 +1,34 @@
+
+var question = {
+ target: "test.me.mywot.com",
+ decodedtarget: "test.me.name.mywot.com",
+ question: {
+ id: 9999,
+ text: "Overall, how satisfied are you with %site%?",
+ choices: [
+ {"value":0, "text":"Extremely dissatisfied"},
+ {"value":1, "text":"Moderately dissatisfied"},
+ {"value":2, "text":"Slightly dissatisfied"},
+ {"value":3, "text":"Neither satisfied nor dissatisfied"},
+ {"value":4, "text":"Slightly satisfied"},
+ {"value":5, "text":"Moderately satisfied"},
+ {"value":6, "text":"Extremely satisfied"}
+ ]
+ }
+};
+
+var encoded = btoa(JSON.stringify(question));
+
+var iframe = document.createElement("iframe");
+
+iframe.setAttribute("id", "wot_surveys_wrapper");
+iframe.setAttribute("scrolling", "no");
+iframe.setAttribute("style", "position: fixed; top: 10px; left: 10px;width: 392px; height: 350px; z-index: 2147483647; border: none;");
+
+iframe.setAttribute("name", encoded);
+
+iframe.setAttribute("src", "./surveys.html");
+
+var body = document.getElementsByTagName("body")[0];
+
+body.appendChild(iframe);
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-mozext/wot.git
More information about the Pkg-mozext-commits
mailing list