<commit_before>define([
	"dojo/_base/array", // array.forEach array.some
	"dojo/aspect",
	"dojo/_base/connect",	// connect._keypress
	"dojo/_base/declare", // declare
	"dojo/dom", // dom.isDescendant
	"dojo/dom-attr", // domAttr.set
	"dojo/dom-construct", // domConstruct.create domConstruct.destroy
	"dojo/dom-geometry", // domGeometry.isBodyLtr
	"dojo/dom-style", // domStyle.set
	"dojo/_base/event", // event.stop
	"dojo/keys",
	"dojo/_base/lang", // lang.hitch
	"dojo/on",
	"dojo/sniff", // has("ie") has("mozilla")
	"./place",
	"./BackgroundIframe",
	"./main"	// dijit (defining dijit.popup to match API doc)
], function(array, aspect, connect, declare, dom, domAttr, domConstruct, domGeometry, domStyle, event, keys, lang, on, has,
			place, BackgroundIframe, dijit){

	// module:
	//		dijit/popup
	// summary:
	//		Used to show drop downs (ex: the select list of a ComboBox)
	//		or popups (ex: right-click context menus)

	/*=====
	var __OpenArgs = function(){
		// popup: Widget
		//		widget to display
		// parent: Widget
		//		the button etc. that is displaying this popup
		// around: DomNode
		//		DOM node (typically a button); place popup relative to this node.  (Specify this *or* "x" and "y" parameters.)
		// x: Integer
		//		Absolute horizontal position (in pixels) to place node at.  (Specify this *or* "around" parameter.)
		// y: Integer
		//		Absolute vertical position (in pixels) to place node at.  (Specify this *or* "around" parameter.)
		// orient: Object|String
		//		When the around parameter is specified, orient should be a list of positions to try, ex:
		//	|	[ "below", "above" ]
		//		For backwards compatibility it can also be an (ordered) hash of tuples of the form
		//		(around-node-corner, popup-node-corner), ex:
		//	|	{ "BL": "TL", "TL": "BL" }
		//		where BL means "bottom left" and "TL" means "top left", etc.
		//
		//		dijit.popup.open() tries to position the popup according to each specified position, in order,
		//		until the popup appears fully within the viewport.
		//
		//		The default value is ["below", "above"]
		//
		//		When an (x,y) position is specified rather than an around node, orient is either
		//		"R" or "L".  R (for right) means that it tries to put the popup to the right of the mouse,
		//		specifically positioning the popup's top-right corner at the mouse position, and if that doesn't
		//		fit in the viewport, then it tries, in order, the bottom-right corner, the top left corner,
		//		and the top-right corner.
		// onCancel: Function
		//		callback when user has canceled the popup by
		//			1. hitting ESC or
		//			2. by using the popup widget's proprietary cancel mechanism (like a cancel button in a dialog);
		//			   i.e. whenever popupWidget.onCancel() is called, args.onCancel is called
		// onClose: Function
		//		callback whenever this popup is closed
		// onExecute: Function
		//		callback when user "executed" on the popup/sub-popup by selecting a menu choice, etc. (top menu only)
		// padding: place.__Position
		//		adding a buffer around the opening position. This is only useful when around is not set.
		this.popup = popup;
		this.parent = parent;
		this.around = around;
		this.x = x;
		this.y = y;
		this.orient = orient;
		this.onCancel = onCancel;
		this.onClose = onClose;
		this.onExecute = onExecute;
		this.padding = padding;
	};

	dijit.popup = {
		// summary:
		//		Used to show drop downs (ex: the select list of a ComboBox)
		//		or popups (ex: right-click context menus).
		//
		//		Access via require(["dijit/popup"], function(popup){ ... }).

		moveOffScreen: function(widget){
			// summary:
			//		Moves the popup widget off-screen.
			//		Do not use this method to hide popups when not in use, because
			//		that will create an accessibility issue: the offscreen popup is
			//		still in the tabbing order.
			// widget: dijit._WidgetBase
			//		The widget
		},

		hide: function(widget){
			// summary:
			//		Hide this popup widget (until it is ready to be shown).
			//		Initialization for widgets that will be used as popups
			//
			//		Also puts widget inside a wrapper DIV (if not already in one)
			//
			//		If popup widget needs to layout it should
			//		do so when it is made visible, and popup._onShow() is called.
			// widget: dijit._WidgetBase
			//		The widget
		},

		open: function(args){
			// summary:
			//		Popup the widget at the specified position
			// example:
			//		opening at the mouse position
			//		|		popup.open({popup: menuWidget, x: evt.pageX, y: evt.pageY});
			// example:
			//		opening the widget as a dropdown
			//		|		popup.open({parent: this, popup: menuWidget, around: this.domNode, onClose: function(){...}});
			//
			//		Note that whatever widget called dijit.popup.open() should also listen to its own _onBlur callback
			//		(fired from _base/focus.js) to know that focus has moved somewhere else and thus the popup should be closed.
			// args: __OpenArgs
			//		Parameters
			return {};	// Object specifying which position was chosen
		},

		close: function(popup){
			// summary:
			//		Close specified popup and any popups that it parented.
			//		If no popup is specified, closes all popups.
			// widget: dijit._WidgetBase?
			//		The widget, optional
		}
	};
	=====*/

	var PopupManager = declare(null, {
		// _stack: dijit._Widget[]
		//		Stack of currently popped up widgets.
		//		(someone opened _stack[0], and then it opened _stack[1], etc.)
		_stack: [],

		// _beginZIndex: Number
		//		Z-index of the first popup.   (If first popup opens other
		//		popups they get a higher z-index.)
		_beginZIndex: 1000,

		_idGen: 1,

		_createWrapper: function(/*Widget*/ widget){
			// summary:
			//		Initialization for widgets that will be used as popups.
			//		Puts widget inside a wrapper DIV (if not already in one),
			//		and returns pointer to that wrapper DIV.

			var wrapper = widget._popupWrapper,
				node = widget.domNode;

			if(!wrapper){
				// Create wrapper <div> for when this widget [in the future] will be used as a popup.
				// This is done early because of IE bugs where creating/moving DOM nodes causes focus
				// to go wonky, see tests/robot/Toolbar.html to reproduce
				wrapper = domConstruct.create("div",{
					"class":"dijitPopup",
					style:{ display: "none"},
					role: "presentation"
				}, widget.ownerDocumentBody);
				wrapper.appendChild(node);

				var s = node.style;
				s.display = "";
				s.visibility = "";
				s.position = "";
				s.top = "0px";

				widget._popupWrapper = wrapper;
				aspect.after(widget, "destroy", function(){
					domConstruct.destroy(wrapper);
					delete widget._popupWrapper;
				});
			}

			return wrapper;
		},

		moveOffScreen: function(/*Widget*/ widget){
			// summary:
			//		Moves the popup widget off-screen.
			//		Do not use this method to hide popups when not in use, because
			//		that will create an accessibility issue: the offscreen popup is
			//		still in the tabbing order.

			// Create wrapper if not already there
			var wrapper = this._createWrapper(widget);

			domStyle.set(wrapper, {
				visibility: "hidden",
				top: "-9999px",		// prevent transient scrollbar causing misalign (#5776), and initial flash in upper left (#10111)
				display: ""
			});
		},

		hide: function(/*Widget*/ widget){
			// summary:
			//		Hide this popup widget (until it is ready to be shown).
			//		Initialization for widgets that will be used as popups
			//
			//		Also puts widget inside a wrapper DIV (if not already in one)
			//
			//		If popup widget needs to layout it should
			//		do so when it is made visible, and popup._onShow() is called.

			// Create wrapper if not already there
			var wrapper = this._createWrapper(widget);

			domStyle.set(wrapper, "display", "none");
		},

		getTopPopup: function(){
			// summary:
			//		Compute the closest ancestor popup that's *not* a child of another popup.
			//		Ex: For a TooltipDialog with a button that spawns a tree of menus, find the popup of the button.
			var stack = this._stack;
			for(var pi=stack.length-1; pi > 0 && stack[pi].parent === stack[pi-1].widget; pi--){
				/* do nothing, just trying to get right value for pi */
			}
			return stack[pi];
		},

		open: function(/*dijit.popup.__OpenArgs*/ args){
			// summary:
			//		Popup the widget at the specified position
			//
			// example:
			//		opening at the mouse position
			//		|		popup.open({popup: menuWidget, x: evt.pageX, y: evt.pageY});
			//
			// example:
			//		opening the widget as a dropdown
			//		|		popup.open({parent: this, popup: menuWidget, around: this.domNode, onClose: function(){...}});
			//
			//		Note that whatever widget called dijit.popup.open() should also listen to its own _onBlur callback
			//		(fired from _base/focus.js) to know that focus has moved somewhere else and thus the popup should be closed.

			var stack = this._stack,
				widget = args.popup,
				orient = args.orient || ["below", "below-alt", "above", "above-alt"],
				ltr = args.parent ? args.parent.isLeftToRight() : domGeometry.isBodyLtr(widget.ownerDocument),
				around = args.around,
				id = (args.around && args.around.id) ? (args.around.id+"_dropdown") : ("popup_"+this._idGen++);

			// If we are opening a new popup that isn't a child of a currently opened popup, then
			// close currently opened popup(s).   This should happen automatically when the old popups
			// gets the _onBlur() event, except that the _onBlur() event isn't reliable on IE, see [22198].
			while(stack.length && (!args.parent || !dom.isDescendant(args.parent.domNode, stack[stack.length-1].widget.domNode))){
				this.close(stack[stack.length-1].widget);
			}

			// Get pointer to popup wrapper, and create wrapper if it doesn't exist
			var wrapper = this._createWrapper(widget);

			domAttr.set(wrapper, {
				id: id,
				style: {
					zIndex: this._beginZIndex + stack.length
				},
				"class": "dijitPopup " + (widget.baseClass || widget["class"] || "").split(" ")[0] +"Popup",
				dijitPopupParent: args.parent ? args.parent.id : ""
			});

			if(has("ie") || has("mozilla")){
				if(!widget.bgIframe){
					// setting widget.bgIframe triggers cleanup in _Widget.destroy()
					widget.bgIframe = new BackgroundIframe(wrapper);
				}
			}

			// position the wrapper node and make it visible
			var best = around ?
				place.around(wrapper, around, orient, ltr, widget.orient ? lang.hitch(widget, "orient") : null) :
				place.at(wrapper, args, orient == 'R' ? ['TR','BR','TL','BL'] : ['TL','BL','TR','BR'], args.padding);

			wrapper.style.display = "";
			wrapper.style.visibility = "visible";
			widget.domNode.style.visibility = "visible";	// counteract effects from _HasDropDown

			var handlers = [];

			// provide default escape and tab key handling
			// (this will work for any widget, not just menu)
			handlers.push(on(wrapper, connect._keypress, lang.hitch(this, function(evt){
				if(evt.charOrCode == keys.ESCAPE && args.onCancel){
					event.stop(evt);
					args.onCancel();
				}else if(evt.charOrCode === keys.TAB){
					event.stop(evt);
					var topPopup = this.getTopPopup();
					if(topPopup && topPopup.onCancel){
						topPopup.onCancel();
					}
				}
			})));

			// watch for cancel/execute events on the popup and notify the caller
			// (for a menu, "execute" means clicking an item)
			if(widget.onCancel && args.onCancel){
				handlers.push(widget.on("cancel", args.onCancel));
			}

			handlers.push(widget.on(widget.onExecute ? "execute" : "change", lang.hitch(this, function(){
				var topPopup = this.getTopPopup();
				if(topPopup && topPopup.onExecute){
					topPopup.onExecute();
				}
			})));

			stack.push({
				widget: widget,
				parent: args.parent,
				onExecute: args.onExecute,
				onCancel: args.onCancel,
				onClose: args.onClose,
				handlers: handlers
			});

			if(widget.onOpen){
				// TODO: in 2.0 standardize onShow() (used by StackContainer) and onOpen() (used here)
				widget.onOpen(best);
			}

			return best;
		},

		close: function(/*Widget?*/ popup){
			// summary:
			//		Close specified popup and any popups that it parented.
			//		If no popup is specified, closes all popups.

			var stack = this._stack;

			// Basically work backwards from the top of the stack closing popups
			// until we hit the specified popup, but IIRC there was some issue where closing
			// a popup would cause others to close too.  Thus if we are trying to close B in [A,B,C]
			// closing C might close B indirectly and then the while() condition will run where stack==[A]...
			// so the while condition is constructed defensively.
			while((popup && array.some(stack, function(elem){return elem.widget == popup;})) ||
				(!popup && stack.length)){
				var top = stack.pop(),
					widget = top.widget,
					onClose = top.onClose;

				if(widget.onClose){
					// TODO: in 2.0 standardize onHide() (used by StackContainer) and onClose() (used here)
					widget.onClose();
				}

				var h;
				while(h = top.handlers.pop()){ h.remove(); }

				// Hide the widget and it's wrapper unless it has already been destroyed in above onClose() etc.
				if(widget && widget.domNode){
					this.hide(widget);
				}

				if(onClose){
					onClose();
				}
			}
		}
	});

	return (dijit.popup = new PopupManager());
});
<commit_msg>Fix !ComboBox memory leak on IE8 on page refresh:<commit_after>define([
	"dojo/_base/array", // array.forEach array.some
	"dojo/aspect",
	"dojo/_base/connect",	// connect._keypress
	"dojo/_base/declare", // declare
	"dojo/dom", // dom.isDescendant
	"dojo/dom-attr", // domAttr.set
	"dojo/dom-construct", // domConstruct.create domConstruct.destroy
	"dojo/dom-geometry", // domGeometry.isBodyLtr
	"dojo/dom-style", // domStyle.set
	"dojo/_base/event", // event.stop
	"dojo/keys",
	"dojo/_base/lang", // lang.hitch
	"dojo/on",
	"dojo/sniff", // has("ie") has("mozilla")
	"./place",
	"./BackgroundIframe",
	"./main"	// dijit (defining dijit.popup to match API doc)
], function(array, aspect, connect, declare, dom, domAttr, domConstruct, domGeometry, domStyle, event, keys, lang, on, has,
			place, BackgroundIframe, dijit){

	// module:
	//		dijit/popup
	// summary:
	//		Used to show drop downs (ex: the select list of a ComboBox)
	//		or popups (ex: right-click context menus)

	/*=====
	var __OpenArgs = function(){
		// popup: Widget
		//		widget to display
		// parent: Widget
		//		the button etc. that is displaying this popup
		// around: DomNode
		//		DOM node (typically a button); place popup relative to this node.  (Specify this *or* "x" and "y" parameters.)
		// x: Integer
		//		Absolute horizontal position (in pixels) to place node at.  (Specify this *or* "around" parameter.)
		// y: Integer
		//		Absolute vertical position (in pixels) to place node at.  (Specify this *or* "around" parameter.)
		// orient: Object|String
		//		When the around parameter is specified, orient should be a list of positions to try, ex:
		//	|	[ "below", "above" ]
		//		For backwards compatibility it can also be an (ordered) hash of tuples of the form
		//		(around-node-corner, popup-node-corner), ex:
		//	|	{ "BL": "TL", "TL": "BL" }
		//		where BL means "bottom left" and "TL" means "top left", etc.
		//
		//		dijit.popup.open() tries to position the popup according to each specified position, in order,
		//		until the popup appears fully within the viewport.
		//
		//		The default value is ["below", "above"]
		//
		//		When an (x,y) position is specified rather than an around node, orient is either
		//		"R" or "L".  R (for right) means that it tries to put the popup to the right of the mouse,
		//		specifically positioning the popup's top-right corner at the mouse position, and if that doesn't
		//		fit in the viewport, then it tries, in order, the bottom-right corner, the top left corner,
		//		and the top-right corner.
		// onCancel: Function
		//		callback when user has canceled the popup by
		//			1. hitting ESC or
		//			2. by using the popup widget's proprietary cancel mechanism (like a cancel button in a dialog);
		//			   i.e. whenever popupWidget.onCancel() is called, args.onCancel is called
		// onClose: Function
		//		callback whenever this popup is closed
		// onExecute: Function
		//		callback when user "executed" on the popup/sub-popup by selecting a menu choice, etc. (top menu only)
		// padding: place.__Position
		//		adding a buffer around the opening position. This is only useful when around is not set.
		this.popup = popup;
		this.parent = parent;
		this.around = around;
		this.x = x;
		this.y = y;
		this.orient = orient;
		this.onCancel = onCancel;
		this.onClose = onClose;
		this.onExecute = onExecute;
		this.padding = padding;
	};

	dijit.popup = {
		// summary:
		//		Used to show drop downs (ex: the select list of a ComboBox)
		//		or popups (ex: right-click context menus).
		//
		//		Access via require(["dijit/popup"], function(popup){ ... }).

		moveOffScreen: function(widget){
			// summary:
			//		Moves the popup widget off-screen.
			//		Do not use this method to hide popups when not in use, because
			//		that will create an accessibility issue: the offscreen popup is
			//		still in the tabbing order.
			// widget: dijit._WidgetBase
			//		The widget
		},

		hide: function(widget){
			// summary:
			//		Hide this popup widget (until it is ready to be shown).
			//		Initialization for widgets that will be used as popups
			//
			//		Also puts widget inside a wrapper DIV (if not already in one)
			//
			//		If popup widget needs to layout it should
			//		do so when it is made visible, and popup._onShow() is called.
			// widget: dijit._WidgetBase
			//		The widget
		},

		open: function(args){
			// summary:
			//		Popup the widget at the specified position
			// example:
			//		opening at the mouse position
			//		|		popup.open({popup: menuWidget, x: evt.pageX, y: evt.pageY});
			// example:
			//		opening the widget as a dropdown
			//		|		popup.open({parent: this, popup: menuWidget, around: this.domNode, onClose: function(){...}});
			//
			//		Note that whatever widget called dijit.popup.open() should also listen to its own _onBlur callback
			//		(fired from _base/focus.js) to know that focus has moved somewhere else and thus the popup should be closed.
			// args: __OpenArgs
			//		Parameters
			return {};	// Object specifying which position was chosen
		},

		close: function(popup){
			// summary:
			//		Close specified popup and any popups that it parented.
			//		If no popup is specified, closes all popups.
			// widget: dijit._WidgetBase?
			//		The widget, optional
		}
	};
	=====*/

	function destroyWrapper(){
		// summary:
		//		Function to destroy wrapper when popup widget is destroyed.
		//		Left in this scope to avoid memory leak on IE8 on refresh page, see #15206.
		if(this._popupWrapper){
			domConstruct.destroy(this._popupWrapper);
			delete this._popupWrapper;
		}
	}

	var PopupManager = declare(null, {
		// _stack: dijit._Widget[]
		//		Stack of currently popped up widgets.
		//		(someone opened _stack[0], and then it opened _stack[1], etc.)
		_stack: [],

		// _beginZIndex: Number
		//		Z-index of the first popup.   (If first popup opens other
		//		popups they get a higher z-index.)
		_beginZIndex: 1000,

		_idGen: 1,

		_createWrapper: function(/*Widget*/ widget){
			// summary:
			//		Initialization for widgets that will be used as popups.
			//		Puts widget inside a wrapper DIV (if not already in one),
			//		and returns pointer to that wrapper DIV.

			var wrapper = widget._popupWrapper,
				node = widget.domNode;

			if(!wrapper){
				// Create wrapper <div> for when this widget [in the future] will be used as a popup.
				// This is done early because of IE bugs where creating/moving DOM nodes causes focus
				// to go wonky, see tests/robot/Toolbar.html to reproduce
				wrapper = domConstruct.create("div", {
					"class":"dijitPopup",
					style:{ display: "none"},
					role: "presentation"
				}, widget.ownerDocumentBody);
				wrapper.appendChild(node);

				var s = node.style;
				s.display = "";
				s.visibility = "";
				s.position = "";
				s.top = "0px";

				widget._popupWrapper = wrapper;
				aspect.after(widget, "destroy", destroyWrapper, true);
			}

			return wrapper;
		},

		moveOffScreen: function(/*Widget*/ widget){
			// summary:
			//		Moves the popup widget off-screen.
			//		Do not use this method to hide popups when not in use, because
			//		that will create an accessibility issue: the offscreen popup is
			//		still in the tabbing order.

			// Create wrapper if not already there
			var wrapper = this._createWrapper(widget);

			domStyle.set(wrapper, {
				visibility: "hidden",
				top: "-9999px",		// prevent transient scrollbar causing misalign (#5776), and initial flash in upper left (#10111)
				display: ""
			});
		},

		hide: function(/*Widget*/ widget){
			// summary:
			//		Hide this popup widget (until it is ready to be shown).
			//		Initialization for widgets that will be used as popups
			//
			//		Also puts widget inside a wrapper DIV (if not already in one)
			//
			//		If popup widget needs to layout it should
			//		do so when it is made visible, and popup._onShow() is called.

			// Create wrapper if not already there
			var wrapper = this._createWrapper(widget);

			domStyle.set(wrapper, "display", "none");
		},

		getTopPopup: function(){
			// summary:
			//		Compute the closest ancestor popup that's *not* a child of another popup.
			//		Ex: For a TooltipDialog with a button that spawns a tree of menus, find the popup of the button.
			var stack = this._stack;
			for(var pi=stack.length-1; pi > 0 && stack[pi].parent === stack[pi-1].widget; pi--){
				/* do nothing, just trying to get right value for pi */
			}
			return stack[pi];
		},

		open: function(/*dijit.popup.__OpenArgs*/ args){
			// summary:
			//		Popup the widget at the specified position
			//
			// example:
			//		opening at the mouse position
			//		|		popup.open({popup: menuWidget, x: evt.pageX, y: evt.pageY});
			//
			// example:
			//		opening the widget as a dropdown
			//		|		popup.open({parent: this, popup: menuWidget, around: this.domNode, onClose: function(){...}});
			//
			//		Note that whatever widget called dijit.popup.open() should also listen to its own _onBlur callback
			//		(fired from _base/focus.js) to know that focus has moved somewhere else and thus the popup should be closed.

			var stack = this._stack,
				widget = args.popup,
				orient = args.orient || ["below", "below-alt", "above", "above-alt"],
				ltr = args.parent ? args.parent.isLeftToRight() : domGeometry.isBodyLtr(widget.ownerDocument),
				around = args.around,
				id = (args.around && args.around.id) ? (args.around.id+"_dropdown") : ("popup_"+this._idGen++);

			// If we are opening a new popup that isn't a child of a currently opened popup, then
			// close currently opened popup(s).   This should happen automatically when the old popups
			// gets the _onBlur() event, except that the _onBlur() event isn't reliable on IE, see [22198].
			while(stack.length && (!args.parent || !dom.isDescendant(args.parent.domNode, stack[stack.length-1].widget.domNode))){
				this.close(stack[stack.length-1].widget);
			}

			// Get pointer to popup wrapper, and create wrapper if it doesn't exist
			var wrapper = this._createWrapper(widget);

			domAttr.set(wrapper, {
				id: id,
				style: {
					zIndex: this._beginZIndex + stack.length
				},
				"class": "dijitPopup " + (widget.baseClass || widget["class"] || "").split(" ")[0] +"Popup",
				dijitPopupParent: args.parent ? args.parent.id : ""
			});

			if(has("ie") || has("mozilla")){
				if(!widget.bgIframe){
					// setting widget.bgIframe triggers cleanup in _Widget.destroy()
					widget.bgIframe = new BackgroundIframe(wrapper);
				}
			}

			// position the wrapper node and make it visible
			var best = around ?
				place.around(wrapper, around, orient, ltr, widget.orient ? lang.hitch(widget, "orient") : null) :
				place.at(wrapper, args, orient == 'R' ? ['TR','BR','TL','BL'] : ['TL','BL','TR','BR'], args.padding);

			wrapper.style.display = "";
			wrapper.style.visibility = "visible";
			widget.domNode.style.visibility = "visible";	// counteract effects from _HasDropDown

			var handlers = [];

			// provide default escape and tab key handling
			// (this will work for any widget, not just menu)
			handlers.push(on(wrapper, connect._keypress, lang.hitch(this, function(evt){
				if(evt.charOrCode == keys.ESCAPE && args.onCancel){
					event.stop(evt);
					args.onCancel();
				}else if(evt.charOrCode === keys.TAB){
					event.stop(evt);
					var topPopup = this.getTopPopup();
					if(topPopup && topPopup.onCancel){
						topPopup.onCancel();
					}
				}
			})));

			// watch for cancel/execute events on the popup and notify the caller
			// (for a menu, "execute" means clicking an item)
			if(widget.onCancel && args.onCancel){
				handlers.push(widget.on("cancel", args.onCancel));
			}

			handlers.push(widget.on(widget.onExecute ? "execute" : "change", lang.hitch(this, function(){
				var topPopup = this.getTopPopup();
				if(topPopup && topPopup.onExecute){
					topPopup.onExecute();
				}
			})));

			stack.push({
				widget: widget,
				parent: args.parent,
				onExecute: args.onExecute,
				onCancel: args.onCancel,
				onClose: args.onClose,
				handlers: handlers
			});

			if(widget.onOpen){
				// TODO: in 2.0 standardize onShow() (used by StackContainer) and onOpen() (used here)
				widget.onOpen(best);
			}

			return best;
		},

		close: function(/*Widget?*/ popup){
			// summary:
			//		Close specified popup and any popups that it parented.
			//		If no popup is specified, closes all popups.

			var stack = this._stack;

			// Basically work backwards from the top of the stack closing popups
			// until we hit the specified popup, but IIRC there was some issue where closing
			// a popup would cause others to close too.  Thus if we are trying to close B in [A,B,C]
			// closing C might close B indirectly and then the while() condition will run where stack==[A]...
			// so the while condition is constructed defensively.
			while((popup && array.some(stack, function(elem){return elem.widget == popup;})) ||
				(!popup && stack.length)){
				var top = stack.pop(),
					widget = top.widget,
					onClose = top.onClose;

				if(widget.onClose){
					// TODO: in 2.0 standardize onHide() (used by StackContainer) and onClose() (used here)
					widget.onClose();
				}

				var h;
				while(h = top.handlers.pop()){ h.remove(); }

				// Hide the widget and it's wrapper unless it has already been destroyed in above onClose() etc.
				if(widget && widget.domNode){
					this.hide(widget);
				}

				if(onClose){
					onClose();
				}
			}
		}
	});

	return (dijit.popup = new PopupManager());
});
<|endoftext|>


<commit_before>const cme = chrome.management;
const getI18N = chrome.i18n.getMessage;
const myid = getI18N('@@extension_id');

// Handle undo/redo events
const undoStack = new UndoStack(document.body);

/**
 * GENERATE PAGE
 */
const $searchField = $(`<input placeholder="${ getI18N('searchTxt') }">`);
const eul = $('
');
const $options = $('
');
const $disableAllButton = $(`<button>${ getI18N('disAll') }</button>`);
const $extensionPageButton = $(`<a href="chrome://extensions">${ getI18N('extensionPage') }</a>`);

if (!localStorage.getItem('undo-info-message')) {
	const $undoInfoMessage = $(`<p>${ getI18N('undoInfoMsg')} </p>`);
	const $hideInfoMessage = $(`<a href="#hide">${ getI18N('hideInfoMsg') }</a>`);
	$undoInfoMessage.append($hideInfoMessage);
	$('body').append($undoInfoMessage);
	$hideInfoMessage.click(() => {
		localStorage.setItem('undo-info-message', 1);
		$undoInfoMessage.slideUp(300);
	});
}

$options
	.append($disableAllButton)
	.append($extensionPageButton);

$('body')
	.append($searchField)
	.append($options)
	.append(eul);

$searchField.focus();
window.scrollTo(0, 0); // fix overscroll caused by autofocus

// Generate extension list
getExtensions(extensions => {
	const listHTML = extensions
	.sort((a, b) => {
		if (a.enabled === b.enabled) {
			return a.name.localeCompare(b.name); // sort by name
		}
		return a.enabled  {
	const $extension = $(e.currentTarget).parent();
	const id = $extension.attr('id');
	const wasEnabled = !$extension.hasClass('disabled');
	const toggle = enabled => {
		cme.setEnabled(id, enabled, () => {
			$extension.toggleClass('disabled', !enabled);
			$extension.find('.extName').attr('title', getI18N(enabled ? 'clkDisable' : 'clkEnable'));
		});
	}

	undoStack.do(() => {
		toggle(!wasEnabled);
	}, () => {
		toggle(wasEnabled);
	});
});

// Show extra buttons on right click
eul.on('contextmenu', '.ext', () => {
	$('[hidden]').removeAttr('hidden');
	return false;
});

// Enable uninstall button
eul.on('click', '.extUninstall', e => {
	cme.uninstall(e.currentTarget.parentNode.id);
});

// Enable filtering
$searchField.on('input', function () {
	const extensions = $('#extList li');
	const keywords = this.value.split(' ').filter(s => s.length);
	const hiddenExtensions = extensions.not((i, el) => {
		return keywords.every(word => el.dataset.name.includes(word));
	});
	hiddenExtensions.hide();
	extensions.not(hiddenExtensions).show();
});

// Enable disable all button
$disableAllButton.click(disableAll);

// Enable chrome:// links
$('body').on('click', '[href^="chrome"]', e => {
	chrome.tabs.create({url: e.currentTarget.href});
	return false;
});

// Update list on uninstall
cme.onUninstalled.addListener(id => {
	$(`#${id}`).remove();
});

/**
 * FUNCTIONS
 */
function getIcon(icons, size = 16) {
	// Set fallback icon
	let selectedIcon = 'icon/puzzle.svg';

	// Get retina size if necessary
	size *= window.devicePixelRatio;

	if (icons && icons.length) {
		// Get a large icon closest to the desired size
		icons.reverse().some(icon => {
			if (icon.size < size) {
				return false;
			}
			selectedIcon = icon.url;
		});
	}
	return selectedIcon;
}

function createList(e) {
	const url = e.installType === 'normal' ? `https://chrome.google.com/webstore/detail/${e.id}` : e.homepageUrl;
	return `

			<button class='extName' title='${getI18N('toggleEnable')}'>

				${e.name}
			</button>
			${
				e.optionsUrl ? `

				` : ``
			}
			${
				url ? `

				` : ``
			}

			<button hidden class="extUninstall" title='${ getI18N('uninstall') }' ></button>

	`;
}

function disableAll() {
	getExtensions(extensions => {
		const wereEnabled = extensions.filter(ext => ext.enabled);
		const selector = wereEnabled.map(ext => '#' + ext.id).join(',');
		const $wereEnabled = $(selector);

		undoStack.do(disable => {
			wereEnabled.forEach(extension => {
				cme.setEnabled(extension.id, !disable);
			});
			$wereEnabled.toggleClass('disabled', disable)
		});
	});
}

function getExtensions(callback) {
	cme.getAll(exts => {
		callback(exts.filter(ext => ext.type === 'extension' && ext.id !== myid));
	});
}<commit_msg>incase user input upercase when searching<commit_after>const cme = chrome.management;
const getI18N = chrome.i18n.getMessage;
const myid = getI18N('@@extension_id');

// Handle undo/redo events
const undoStack = new UndoStack(document.body);

/**
 * GENERATE PAGE
 */
const $searchField = $(`<input placeholder="${ getI18N('searchTxt') }">`);
const eul = $('
');
const $options = $('
');
const $disableAllButton = $(`<button>${ getI18N('disAll') }</button>`);
const $extensionPageButton = $(`<a href="chrome://extensions">${ getI18N('extensionPage') }</a>`);

if (!localStorage.getItem('undo-info-message')) {
	const $undoInfoMessage = $(`<p>${ getI18N('undoInfoMsg')} </p>`);
	const $hideInfoMessage = $(`<a href="#hide">${ getI18N('hideInfoMsg') }</a>`);
	$undoInfoMessage.append($hideInfoMessage);
	$('body').append($undoInfoMessage);
	$hideInfoMessage.click(() => {
		localStorage.setItem('undo-info-message', 1);
		$undoInfoMessage.slideUp(300);
	});
}

$options
	.append($disableAllButton)
	.append($extensionPageButton);

$('body')
	.append($searchField)
	.append($options)
	.append(eul);

$searchField.focus();
window.scrollTo(0, 0); // fix overscroll caused by autofocus

// Generate extension list
getExtensions(extensions => {
	const listHTML = extensions
	.sort((a, b) => {
		if (a.enabled === b.enabled) {
			return a.name.localeCompare(b.name); // sort by name
		}
		return a.enabled  {
	const $extension = $(e.currentTarget).parent();
	const id = $extension.attr('id');
	const wasEnabled = !$extension.hasClass('disabled');
	const toggle = enabled => {
		cme.setEnabled(id, enabled, () => {
			$extension.toggleClass('disabled', !enabled);
			$extension.find('.extName').attr('title', getI18N(enabled ? 'clkDisable' : 'clkEnable'));
		});
	};

	undoStack.do(() => {
		toggle(!wasEnabled);
	}, () => {
		toggle(wasEnabled);
	});
});

// Show extra buttons on right click
eul.on('contextmenu', '.ext', () => {
	$('[hidden]').removeAttr('hidden');
	return false;
});

// Enable uninstall button
eul.on('click', '.extUninstall', e => {
	cme.uninstall(e.currentTarget.parentNode.id);
});

// Enable filtering
$searchField.on('input', function () {
	const extensions = $('#extList li');
	const keywords = this.value.split(' ').filter(s => s.length);
	const hiddenExtensions = extensions.not((i, el) => {
		return keywords.every(word => el.dataset.name.includes(word.toLowerCase()));
	});
	hiddenExtensions.hide();
	extensions.not(hiddenExtensions).show();
});

// Enable disable all button
$disableAllButton.click(disableAll);

// Enable chrome:// links
$('body').on('click', '[href^="chrome"]', e => {
	chrome.tabs.create({url: e.currentTarget.href});
	return false;
});

// Update list on uninstall
cme.onUninstalled.addListener(id => {
	$(`#${id}`).remove();
});

/**
 * FUNCTIONS
 */
function getIcon(icons, size = 16) {
	// Set fallback icon
	let selectedIcon = 'icon/puzzle.svg';

	// Get retina size if necessary
	size *= window.devicePixelRatio;

	if (icons && icons.length) {
		// Get a large icon closest to the desired size
		icons.reverse().some(icon => {
			if (icon.size < size) {
				return false;
			}
			selectedIcon = icon.url;
		});
	}
	return selectedIcon;
}

function createList(e) {
	const url = e.installType === 'normal' ? `https://chrome.google.com/webstore/detail/${e.id}` : e.homepageUrl;
	return `

			<button class='extName' title='${getI18N('toggleEnable')}'>

				${e.name}
			</button>
			${
				e.optionsUrl ? `

				` : ``
			}
			${
				url ? `

				` : ``
			}

			<button hidden class="extUninstall" title='${ getI18N('uninstall') }' ></button>

	`;
}

function disableAll() {
	getExtensions(extensions => {
		const wereEnabled = extensions.filter(ext => ext.enabled);
		const selector = wereEnabled.map(ext => '#' + ext.id).join(',');
		const $wereEnabled = $(selector);

		undoStack.do(disable => {
			wereEnabled.forEach(extension => {
				cme.setEnabled(extension.id, !disable);
			});
			$wereEnabled.toggleClass('disabled', disable)
		});
	});
}

function getExtensions(callback) {
	cme.getAll(exts => {
		callback(exts.filter(ext => ext.type === 'extension' && ext.id !== myid));
	});
}<|endoftext|>


<commit_before>
window.browser = (function () {
    return window.msBrowser ||
        window.browser ||
        window.chrome;
})();

var backgroundPage = chrome.extension.getBackgroundPage();
var odv = backgroundPage.odv;
var tabId;

browser.tabs.query({active: true, currentWindow: true},function(tabs){
    tabId = tabs[0].id;
    updateHtml();
});

function updateHtml()
{
    var status = status = odv.tabStatus[tabId];

    if(status < 1) {
        document.getElementById('error_text').innerHTML = "Got no data. Try refeshing the page.";
        document.getElementById('process').style.display = 'none';
        document.getElementById('error').style.display = 'block';
        return;
    }

    if(status < 50) {
        window.setTimeout(updateHtml, 500);
        return;
    }

    if(status === odv.tabStatusNoBusinessId) {
        document.getElementById('error_text').innerHTML = "
The owner of this website is not authenticated.

Do not enter any sensitive personel information, unless you are absolutely sure that you are on a wellknown website and NOT on a imitating phishing site or any other unknown site.
";
        document.getElementById('process').style.display = 'none';
        document.getElementById('error').style.display = 'block';
        return;
    }

    if(status < 99) {
        document.getElementById('error_text').innerHTML = "Error code: " + status;
        document.getElementById('process').style.display = 'none';
        document.getElementById('error').style.display = 'block';
        return;
    }

    var response = odv.tabMeta[tabId].response;

    var id = response.official.id.toUpperCase();

    var countryIso2 = id.substring(0, 2);

    var extrainfoEl = document.getElementById('extrainfo');

    document.getElementById('official_name').innerText = response.official.name;
    document.getElementById('id').innerText = id;
    document.getElementById('countryflag').setAttribute('src', 'https://www.peercraft.com/img/flags/23x15/'+countryIso2+'.png');

    if(response.official.addressLines) {
        var el = document.createElement('p');
        el.innerText = response.official.addressLines.join(", ");
        extrainfoEl.appendChild(el);
    }

    if(response.official.dkEmployees) {
        var el = document.createElement('p');
        if(response.official.dkEmployees.from === response.official.dkEmployees.to) {
            el.innerHTML = "Number of employees: "
                +response.official.dkEmployees.from
                +" ("+response.official.dkEmployees.date+")";
        } else {
            el.innerHTML = "Number of employees: "
                +response.official.dkEmployees.from
                +" to "
                +response.official.dkEmployees.to
                +" ("+response.official.dkEmployees.date+")";
        }
        extrainfoEl.appendChild(el);
    }

    if(response.official.dkStatusTimeline) {
        var el = document.createElement('p');
        var div = document.createElement('div');
        div.innerHTML = "Status Timeline:";
        el.appendChild(div);
        response.official.dkStatusTimeline.forEach(function(item){
            var div = document.createElement('div');
            div.innerText = item.date+" "+item.translated;
            el.appendChild(div);
        });
        extrainfoEl.appendChild(el);
    }

    if(response.official.mainLineOfBusinessNaceV2) {
        var naceV2list = fetchFile('naceV2.json');

        var el = document.createElement('p');
        var div = document.createElement('div');
        div.innerHTML = "Line of Business:";
        el.appendChild(div);;
        var div = document.createElement('div');
        div.innerText = naceV2list[response.official.mainLineOfBusinessNaceV2]
            +" ("+response.official.mainLineOfBusinessNaceV2+")";
        el.appendChild(div);
        extrainfoEl.appendChild(el);
    }

    if(response.official.dkManagement) {
        var el = document.createElement('p');
        var div = document.createElement('div');
        div.innerHTML = "Management:";
        el.appendChild(div);
        response.official.dkManagement.forEach(function(item){
            var div = document.createElement('div');
            div.innerText = item.name;
            el.appendChild(div);
        });
        extrainfoEl.appendChild(el);
    }

    document.getElementById('process').style.display = 'none';
    document.getElementById('success').style.display = 'block';
}

function fetchFile(file) {
    try {
        var xhr = new XMLHttpRequest();

        xhr.open('GET', browser.runtime.getURL(file), false);
        xhr.setRequestHeader("Content-Type", "application/json");
        xhr.send(null);

        if(xhr.status !== 200) {
            return false;
        }

        return JSON.parse(xhr.responseText);
    } catch (ex) {
        console.log(ex);
        return false;
    }
}
<commit_msg>always show "Number of employees" in popup<commit_after>
window.browser = (function () {
    return window.msBrowser ||
        window.browser ||
        window.chrome;
})();

var backgroundPage = chrome.extension.getBackgroundPage();
var odv = backgroundPage.odv;
var tabId;

browser.tabs.query({active: true, currentWindow: true},function(tabs){
    tabId = tabs[0].id;
    updateHtml();
});

function updateHtml()
{
    var status = status = odv.tabStatus[tabId];

    if(status < 1) {
        document.getElementById('error_text').innerHTML = "Got no data. Try refeshing the page.";
        document.getElementById('process').style.display = 'none';
        document.getElementById('error').style.display = 'block';
        return;
    }

    if(status < 50) {
        window.setTimeout(updateHtml, 500);
        return;
    }

    if(status === odv.tabStatusNoBusinessId) {
        document.getElementById('error_text').innerHTML = "
The owner of this website is not authenticated.

Do not enter any sensitive personel information, unless you are absolutely sure that you are on a wellknown website and NOT on a imitating phishing site or any other unknown site.
";
        document.getElementById('process').style.display = 'none';
        document.getElementById('error').style.display = 'block';
        return;
    }

    if(status < 99) {
        document.getElementById('error_text').innerHTML = "Error code: " + status;
        document.getElementById('process').style.display = 'none';
        document.getElementById('error').style.display = 'block';
        return;
    }

    var response = odv.tabMeta[tabId].response;

    var id = response.official.id.toUpperCase();

    var countryIso2 = id.substring(0, 2);

    var extrainfoEl = document.getElementById('extrainfo');

    document.getElementById('official_name').innerText = response.official.name;
    document.getElementById('id').innerText = id;
    document.getElementById('countryflag').setAttribute('src', 'https://www.peercraft.com/img/flags/23x15/'+countryIso2+'.png');

    if(response.official.addressLines) {
        var el = document.createElement('p');
        el.innerText = response.official.addressLines.join(", ");
        extrainfoEl.appendChild(el);
    }

    if(response.official.dkEmployees) {
        var el = document.createElement('p');
        if(response.official.dkEmployees.from === response.official.dkEmployees.to) {
            el.innerHTML = "Number of employees: "
                +response.official.dkEmployees.from
                +" ("+response.official.dkEmployees.date+")";
        } else {
            el.innerHTML = "Number of employees: "
                +response.official.dkEmployees.from
                +" to "
                +response.official.dkEmployees.to
                +" ("+response.official.dkEmployees.date+")";
        }
        extrainfoEl.appendChild(el);
    } else {
        var el = document.createElement('p');
        el.innerHTML = "Number of employees: "
            +" None registered";
        extrainfoEl.appendChild(el);
    }

    if(response.official.dkStatusTimeline) {
        var el = document.createElement('p');
        var div = document.createElement('div');
        div.innerHTML = "Status Timeline:";
        el.appendChild(div);
        response.official.dkStatusTimeline.forEach(function(item){
            var div = document.createElement('div');
            div.innerText = item.date+" "+item.translated;
            el.appendChild(div);
        });
        extrainfoEl.appendChild(el);
    }

    if(response.official.mainLineOfBusinessNaceV2) {
        var naceV2list = fetchFile('naceV2.json');

        var el = document.createElement('p');
        var div = document.createElement('div');
        div.innerHTML = "Line of Business:";
        el.appendChild(div);;
        var div = document.createElement('div');
        div.innerText = naceV2list[response.official.mainLineOfBusinessNaceV2]
            +" ("+response.official.mainLineOfBusinessNaceV2+")";
        el.appendChild(div);
        extrainfoEl.appendChild(el);
    }

    if(response.official.dkManagement) {
        var el = document.createElement('p');
        var div = document.createElement('div');
        div.innerHTML = "Management:";
        el.appendChild(div);
        response.official.dkManagement.forEach(function(item){
            var div = document.createElement('div');
            div.innerText = item.name;
            el.appendChild(div);
        });
        extrainfoEl.appendChild(el);
    }

    document.getElementById('process').style.display = 'none';
    document.getElementById('success').style.display = 'block';
}

function fetchFile(file) {
    try {
        var xhr = new XMLHttpRequest();

        xhr.open('GET', browser.runtime.getURL(file), false);
        xhr.setRequestHeader("Content-Type", "application/json");
        xhr.send(null);

        if(xhr.status !== 200) {
            return false;
        }

        return JSON.parse(xhr.responseText);
    } catch (ex) {
        console.log(ex);
        return false;
    }
}
<|endoftext|>


<commit_before>var groups = [];
var activeGroup;

var defaultGroup = {
  name:"Default Group",
  tabs:[],
  open:false,
  active:true
}

var refreshUI = function(){
  $(".group").remove();
  for(var i = 0;i<groups.length;i++){
      $(".container").append("

"+groups[i].name+"

");
      $("#name"+i).click({groupnum:i},function(event){
	    var i = event.data.groupnum;$(".tab"+i).toggle();});
      $(".group"+i).append("<input class='tab"+i+"' id='remove"+i+"' type='button' value='Remove Group' /><input class='tab"+i+"' id='switch"+i+"' type='button' value='Switch to this Group' />");
      $("#remove"+i).click({groupnum:i},function(event){
            deleteGroup(event.data.groupnum);
        });
      $("#switch"+i).click({groupnum:i},function(event){
            switchGroup(event.data.groupnum);
        });
	  for(var j = 0;j<groups[i].tabs.length;j++){
	    $(".group"+i).append("

"+groups[i].tabs[j].title+" Move

");
        for(var p = 0;p<groups.length;p++)
        {
            $("#tab"+i+"_"+j).append("
Send To "+groups[p].name+"
");
            $("#send"+i+"_"+j+"_"+p).click({currgroup:i,tabnum:j,newgroup:p},function(event){moveTab(event.data.currgroup,event.data.tabnum,event.data.newgroup);});
        }
        $("#tab"+i+"_"+j).toggle(false);
        $("#move"+i+"_"+j).click({groupnum:i,tabnum:j},function(event){$("#tab"+event.data.groupnum+"_"+event.data.tabnum).toggle();});
      }
      $(".tab"+i).toggle(false);
	}
};

$(document).ready(function(){
  $(".create").click(function(event){
     createGroup($("#name").val());
  });

  chrome.tabs.query({currentWindow:true},function(tabarray){
    chrome.storage.local.get(['groups','activeGroup'],function(items){
      defaultGroup.tabs = tabarray;
      if(items.groups){
        groups = items.groups;
        console.log('Restoring Saved Groups');
      } else {
        groups = [defaultGroup];
        console.log('No Saved Groups Found');
      }
      if(items.activeGroup){
        console.log('Restoring Saved Active Group');
        activeGroup = items.activeGroup;
      } else {
        console.log('No Saved Active Group Found, defaulting to 0');
        activeGroup = 0;
      }
      groups[activeGroup].tabs = tabarray;
      refreshUI();
    });
  });
});

function storeGroups(){
  chrome.storage.local.set({'groups': groups,'activeGroup':activeGroup},refreshUI);
}

// IMPORTANT:  this could be totally wrong.
// I have no idea how to test.  Plz help.
//
// Intended to move tab from one group to another
// If the source or dest group is open, modifies tab state
// Does nothing with the UI at this time
function moveTab(sourceGroupID, tabID, destGroupID){
  var destGroup, sourceGroup, tab;
  sourceGroup = groups[sourceGroupID];
  destGroup = groups[destGroupID];
  tab = sourceGroup.tabs[tabID];
  if(sourceGroupID===activeGroup){
    chrome.tabs.remove(tab.id);
  }
  console.log(tab);
  destGroup.tabs.push(tab);
  sourceGroup.tabs.splice(tabID,1);
  storeGroups();
  if(destGroupID===activeGroup){
    chrome.tabs.create({url:tab.url,selected:false});
  }
}

//Used for switching in between groups
//Check me on this
//Can be used to switch in between groups using a keyboard shortcut
function switchGroup(groupID) {
  var destGroups = groups[groupID];
  var tabs = groups[activeGroup].tabs;
  //Creates tabs and selects the first tab
  for (var k = 0; k < destGroups.tabs.length; k++) {
      if (k = 0) chrome.tabs.create({url:destGroups.tabs[k].url,selected:true});
      else chrome.tabs.create({url:destGroups.tabs[k].url,selected:false});
  }
  for (var j = 0; j < tabs.length; j++){
	    chrome.tabs.remove(tabs[j].id);
  }
  activeGroup = groupID;
  storeGroups();
  destGroups.active = true;
}

//Um. I probably have to fix everything here.
//Used for creating a group
function createGroup(groupName){
  var newGroup = {
  name: groupName,
  tabs:[],
  open:false,
  active:true
  }
  groups.push(newGroup);
  storeGroups();
}

//Used for deleting a group
function deleteGroup(groupID){
    //TODO: UPDATE ACTIVE GROUP ID IF NEEDED
  	groups.splice(groupID,1);
    storeGroups();
}

//Used for naming a group
function nameGroup(groupID,label){
      groups[groupID].name=label;
      storeGroups();
}
<commit_msg>Fixed switch<commit_after>var groups = [];
var activeGroup;

var defaultGroup = {
  name:"Default Group",
  tabs:[],
  open:false,
  active:true
}

var refreshUI = function(){
  $(".group").remove();
  for(var i = 0;i<groups.length;i++){
      $(".container").append("

"+groups[i].name+"

");
      $("#name"+i).click({groupnum:i},function(event){
	    var i = event.data.groupnum;$(".tab"+i).toggle();});
      $(".group"+i).append("<input class='tab"+i+"' id='remove"+i+"' type='button' value='Remove Group' /><input class='tab"+i+"' id='switch"+i+"' type='button' value='Switch to this Group' />");
      $("#remove"+i).click({groupnum:i},function(event){
            deleteGroup(event.data.groupnum);
        });
      $("#switch"+i).click({groupnum:i},function(event){
            switchGroup(event.data.groupnum);
        });
	  for(var j = 0;j<groups[i].tabs.length;j++){
	    $(".group"+i).append("

"+groups[i].tabs[j].title+" Move

");
        for(var p = 0;p<groups.length;p++)
        {
            $("#tab"+i+"_"+j).append("
Send To "+groups[p].name+"
");
            $("#send"+i+"_"+j+"_"+p).click({currgroup:i,tabnum:j,newgroup:p},function(event){moveTab(event.data.currgroup,event.data.tabnum,event.data.newgroup);});
        }
        $("#tab"+i+"_"+j).toggle(false);
        $("#move"+i+"_"+j).click({groupnum:i,tabnum:j},function(event){$("#tab"+event.data.groupnum+"_"+event.data.tabnum).toggle();});
      }
      $(".tab"+i).toggle(false);
	}
};

$(document).ready(function(){
  $(".create").click(function(event){
     createGroup($("#name").val());
  });

  chrome.tabs.query({currentWindow:true},function(tabarray){
    chrome.storage.local.get(['groups','activeGroup'],function(items){
      defaultGroup.tabs = tabarray;
      if(items.groups){
        groups = items.groups;
        console.log('Restoring Saved Groups');
      } else {
        groups = [defaultGroup];
        console.log('No Saved Groups Found');
      }
      if(items.activeGroup){
        console.log('Restoring Saved Active Group');
        activeGroup = items.activeGroup;
      } else {
        console.log('No Saved Active Group Found, defaulting to 0');
        activeGroup = 0;
      }
      groups[activeGroup].tabs = tabarray;
      refreshUI();
    });
  });
});

function storeGroups(){
  chrome.storage.local.set({'groups': groups,'activeGroup':activeGroup},refreshUI);
}

// IMPORTANT:  this could be totally wrong.
// I have no idea how to test.  Plz help.
//
// Intended to move tab from one group to another
// If the source or dest group is open, modifies tab state
// Does nothing with the UI at this time
function moveTab(sourceGroupID, tabID, destGroupID){
  var destGroup, sourceGroup, tab;
  sourceGroup = groups[sourceGroupID];
  destGroup = groups[destGroupID];
  tab = sourceGroup.tabs[tabID];
  if(sourceGroupID===activeGroup){
    chrome.tabs.remove(tab.id);
  }
  console.log(tab);
  destGroup.tabs.push(tab);
  sourceGroup.tabs.splice(tabID,1);
  storeGroups();
  if(destGroupID===activeGroup){
    chrome.tabs.create({url:tab.url,selected:false});
  }
}

//Used for switching in between groups
//Check me on this
//Can be used to switch in between groups using a keyboard shortcut
function switchGroup(groupID) {
  var destGroups = groups[groupID];
  var tabs = groups[activeGroup].tabs;
  //Creates tabs and selects the first tab
  var p = destGroups.tabs.length
  for (var k = 0; k < p; k++) {
      if (k === 0) chrome.tabs.create({url:destGroups.tabs[k].url,selected:true});
      else chrome.tabs.create({url:destGroups.tabs[k].url,selected:false});
  }
  var q = tabs.length
  for (var j = 0; j < q; j++){
	    chrome.tabs.remove(tabs[j].id);
  }
  activeGroup = groupID;
  storeGroups();
  destGroups.active = true;
}

//Um. I probably have to fix everything here.
//Used for creating a group
function createGroup(groupName){
  var newGroup = {
  name: groupName,
  tabs:[],
  open:false,
  active:true
  }
  groups.push(newGroup);
  storeGroups();
}

//Used for deleting a group
function deleteGroup(groupID){
    //TODO: UPDATE ACTIVE GROUP ID IF NEEDED
  	groups.splice(groupID,1);
    storeGroups();
}

//Used for naming a group
function nameGroup(groupID,label){
      groups[groupID].name=label;
      storeGroups();
}
<|endoftext|>


<commit_before>RESP = $('#title');
URL = "https://student.p4ed.com/notas/dados/";

function _get_bonus(data, bimester)
{
    notas = data.Dados.DadosPagina.Materias[5].Frentes[0].NotasBimestre;
    if (notas.length <= bimester)
    {
        return 0;
    }
    return parseFloat(notas[bimester].Provas[0].Nota.replace(',', '.'));
}

function success(data)
{
    if (!data.Sucesso)
    {
        return displayError();
    }

    $('#title').html(data.Dados.Pessoa.Nome);
    $('#slider').show();
    $('#bimester').on('change', function()
                      {
                        setBimester(this.value);
                      });

    bonusList = [0, 0, 0, 0];
    for (i = 0; i < 4; i++)
    {
        bonusList[i] = _get_bonus(data, i);
    }

    $.each(data.Dados.DadosPagina.Materias, function (_, subject)
    {
        handleSubject(subject, bonusList);
    });

    setBimester(data.Dados.DadosPagina.Ciclos.length - 1);
}

function handleSubject(data, bonusList)
{
    if (data.Frentes.length)
    {
        $.each(data.Frentes, function (_, front)
        {
            handleFront(front, bonusList);
        });
    }

    else
    {
        handleFront(data, bonusList);
    }
}

function handleFront(data, bonusList)
{
    blacklist = ['Red', 'Rubeval', 'Victor Amorim'];
    blacklisted = false;
    $.each(blacklist, function (_, name)
    {
        blacklisted |= (data.Nome.indexOf(name) != -1);
    });

    if (!blacklisted)
    {
        grades = data.NotasBimestre.slice(0); // Make a copy

        // Parse grades
        hasTests = false;
        bimesters = [];

        for (i = 0; i < 4; i++)
        {
            grades.push({});
            bd = grades.shift();

            p1 = undefined;
            p2 = undefined;
            bonus = bonusList[i];

            if (bd && bd.Provas && bd.Provas.length)
            {
                hasTests = true;
                $.each(bd.Provas, function (_, test)
                {
                    if (test.Nome.indexOf('Prova 1') != -1)
                    {
                        p1 = parseFloat(test.Nota.replace(',', '.'))
                    }

                    else if (test.Nome.indexOf('Prova 2') != -1 && test.Respostas && test.Respostas.length)
                    {
                        p2 = parseFloat(test.Nota.replace(',', '.'))
                    }
                });
            }

            bimesters.push([p1, p2, bonus]);
        }

        if (hasTests)
        {
            for (i = 0; i < 4; i++)
            {
                boxHTML = '

' + data.Nome + ' - ' + (i + 1) + 'º Bimestre' + '
';

                bim = bimesters[i];
                p1 = bim[0];
                p2 = bim[1];
                bonus = bim[2];

                if (p1 == undefined)
                {
                    // Got no grades at all
                    boxHTML += '
Sem dados
';
                }

                else if (p2 == undefined)
                {
                    boxHTML += '
P1: ' + p1 + '
P2:';
                    // Calc how many questions you need
                    missingPoints = 12 - p1 - bonus * 2;
                    boxHTML += ' você precisa de ' + missingPoints + ' ponto' + ((missingPoints >= 2) ? 's': '') + '
';

                    if (missingPoints > 10)
                    {
                        // Too bad
                        boxHTML += 'Situação irrecuperável, você já está de rec.';
                    }

                    else
                    {
                        lastMin = 0;
                        gotFirstGroup = 0;
                        temp = []
                        for (j = 3; j <= 24; j++)
                        {
                            value = 10.0 / j;
                            qmin = Math.ceil(missingPoints / value);
                            if (qmin > lastMin || j == 24)
                            {
                                if (gotFirstGroup)
                                {
                                    if (temp.length > 1)
                                    {
                                        last = temp.pop();
                                        temp.push('ou');
                                        temp.push(last);
                                        numsStr = temp.join(', ').replace(', ou,', ' ou');
                                    }

                                    else
                                    {
                                    numsStr = temp[0];
                                    }

                                    boxHTML += (gotFirstGroup > 1) ? '
' : '';
                                    boxHTML += 'Para ' + numsStr + ' questões, você precisa acertar ' + lastMin;
                                    temp = [];
                                }

                                gotFirstGroup++;
                            }

                            lastMin = qmin;
                            temp.push(j);
                        }
                    }
                }

                else
                {
                    boxHTML += '
P1: ' + p1 + '
P2: ' + p2 + '
= 0) ? 'verde">Situação: aprovado
' : 'vermelho">Situação: Rec por ' + Math.absd(dt) + ' pontos
';
                }

                boxHTML += '
';

                $('#main').append(boxHTML);
            }
        }
    }
}

function setBimester(bimester)
{
    for (i = 0; i < 4; i++)
    {
        element = $('.bim_' + i);
        if (i == bimester)
        {
            element.show();
        }

        else
        {
            element.hide();
        }
    }
}

function handleError(handler, status, error)
{
    RESP.innerHTML = "Sinto muito, ocorreu um erro. Tente novamente.";
}

$.ajax({
  dataType: "json",
  url: URL,
  success: success,
  error: handleError
});
<commit_msg>Update popup.js<commit_after>RESP = $('#title');
URL = "https://student.p4ed.com/notas/dados/";

function _get_bonus(data, bimester)
{
    notas = data.Dados.DadosPagina.Materias[5].Frentes[0].NotasBimestre;
    if (notas.length <= bimester)
    {
        return 0;
    }
    return parseFloat(notas[bimester].Provas[0].Nota.replace(',', '.'));
}

function success(data)
{
    if (!data.Sucesso)
    {
        return displayError();
    }

    $('#title').html(data.Dados.Pessoa.Nome);
    $('#slider').show();
    $('#bimester').on('change', function()
                      {
                        setBimester(this.value);
                      });

    bonusList = [0, 0, 0, 0];
    for (i = 0; i < 4; i++)
    {
        bonusList[i] = _get_bonus(data, i);
    }

    $.each(data.Dados.DadosPagina.Materias, function (_, subject)
    {
        handleSubject(subject, bonusList);
    });

    setBimester(data.Dados.DadosPagina.Ciclos.length - 1);
}

function handleSubject(data, bonusList)
{
    if (data.Frentes.length)
    {
        $.each(data.Frentes, function (_, front)
        {
            handleFront(front, bonusList);
        });
    }

    else
    {
        handleFront(data, bonusList);
    }
}

function handleFront(data, bonusList)
{
    blacklist = ['Red', 'Rubeval', 'Victor Amorim'];
    blacklisted = false;
    $.each(blacklist, function (_, name)
    {
        blacklisted |= (data.Nome.indexOf(name) != -1);
    });

    if (!blacklisted)
    {
        grades = data.NotasBimestre.slice(0); // Make a copy

        // Parse grades
        hasTests = false;
        bimesters = [];

        for (i = 0; i < 4; i++)
        {
            grades.push({});
            bd = grades.shift();

            p1 = undefined;
            p2 = undefined;
            bonus = bonusList[i];

            if (bd && bd.Provas && bd.Provas.length)
            {
                hasTests = true;
                $.each(bd.Provas, function (_, test)
                {
                    if (test.Nome.indexOf('Prova 1') != -1)
                    {
                        p1 = parseFloat(test.Nota.replace(',', '.'))
                    }

                    else if (test.Nome.indexOf('Prova 2') != -1 && test.Respostas && test.Respostas.length)
                    {
                        p2 = parseFloat(test.Nota.replace(',', '.'))
                    }
                });
            }

            bimesters.push([p1, p2, bonus]);
        }

        if (hasTests)
        {
            for (i = 0; i < 4; i++)
            {
                boxHTML = '

' + data.Nome + ' - ' + (i + 1) + 'º Bimestre' + '
';

                bim = bimesters[i];
                p1 = bim[0];
                p2 = bim[1];
                bonus = bim[2];

                if (p1 == undefined)
                {
                    // Got no grades at all
                    boxHTML += '
Sem dados
';
                }

                else if (p2 == undefined)
                {
                    boxHTML += '
P1: ' + p1 + '
P2:';
                    // Calc how many questions you need
                    missingPoints = 12 - p1 - bonus * 2;
                    boxHTML += ' você precisa de ' + missingPoints + ' ponto' + ((missingPoints >= 2) ? 's': '') + '
';

                    if (missingPoints > 10)
                    {
                        // Too bad
                        boxHTML += 'Situação irrecuperável, você já está de rec.';
                    }

                    else
                    {
                        lastMin = 0;
                        gotFirstGroup = 0;
                        temp = []
                        for (j = 3; j <= 24; j++)
                        {
                            value = 10.0 / j;
                            qmin = Math.ceil(missingPoints / value);
                            if (qmin > lastMin || j == 24)
                            {
                                if (gotFirstGroup)
                                {
                                    if (temp.length > 1)
                                    {
                                        last = temp.pop();
                                        temp.push('ou');
                                        temp.push(last);
                                        numsStr = temp.join(', ').replace(', ou,', ' ou');
                                    }

                                    else
                                    {
                                    numsStr = temp[0];
                                    }

                                    boxHTML += (gotFirstGroup > 1) ? '
' : '';
                                    boxHTML += 'Para ' + numsStr + ' questões, você precisa acertar ' + lastMin;
                                    temp = [];
                                }

                                gotFirstGroup++;
                            }

                            lastMin = qmin;
                            temp.push(j);
                        }
                    }
                }

                else
                {
                    boxHTML += '
P1: ' + p1 + '
P2: ' + p2 + '
= 0) ? 'verde">Situação: aprovado
' : 'vermelho">Situação: Rec por ' + dt + ' pontos
';
                }

                boxHTML += '
';

                $('#main').append(boxHTML);
            }
        }
    }
}

function setBimester(bimester)
{
    for (i = 0; i < 4; i++)
    {
        element = $('.bim_' + i);
        if (i == bimester)
        {
            element.show();
        }

        else
        {
            element.hide();
        }
    }
}

function handleError(handler, status, error)
{
    RESP.innerHTML = "Sinto muito, ocorreu um erro. Tente novamente.";
}

$.ajax({
  dataType: "json",
  url: URL,
  success: success,
  error: handleError
});
<|endoftext|>


<commit_before>var httpProxy = require('http-proxy');

httpProxy.createServer({
  router: {
    'anoside.com/images': 'localhost:8000',
    'anoside.com/javascripts': 'localhost:8000',
    'anoside.com/stylesheets': 'localhost:8000',
    'anoside.com/favicon.png': 'localhost:8000',
    'localhost/socket.io': 'localhost:4000',
    'anoside.com/*': 'localhost:3000',
    'localhost/socket.io': 'localhost:4000'
  }
}).listen(80);<commit_msg>update<commit_after>var httpProxy = require('http-proxy');

httpProxy.createServer({
  router: {
    'anoside.com/images': 'localhost:8000',
    'anoside.com/javascripts': 'localhost:8000',
    'anoside.com/stylesheets': 'localhost:8000',
    'anoside.com/favicon.png': 'localhost:8000',
    '/socket.io': 'localhost:4000',
    'anoside.com/*': 'localhost:3000'
  }
}).listen(80);<|endoftext|>


<commit_before>/*
 * Copyright (C) 2012 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * @param {WebInspector.TimelinePanel} timelinePanel
 * @param {WebInspector.TimelineModel} model
 * @param {number} sidebarWidth
 * @constructor
 */
WebInspector.MemoryStatistics = function(timelinePanel, model, sidebarWidth)
{
    this._timelinePanel = timelinePanel;
    this._counters = [];

    model.addEventListener(WebInspector.TimelineModel.Events.RecordAdded, this._onRecordAdded, this);
    model.addEventListener(WebInspector.TimelineModel.Events.RecordsCleared, this._onRecordsCleared, this);

    this._containerAnchor = timelinePanel.element.lastChild;
    this._memorySidebarView = new WebInspector.SidebarView(WebInspector.SidebarView.SidebarPosition.Start, undefined, sidebarWidth);
    this._memorySidebarView.sidebarElement.addStyleClass("sidebar");
    this._memorySidebarView.element.id = "memory-graphs-container";

    this._memorySidebarView.addEventListener(WebInspector.SidebarView.EventTypes.Resized, this._sidebarResized.bind(this));

    this._canvasContainer = this._memorySidebarView.mainElement;
    this._canvasContainer.id = "memory-graphs-canvas-container";
    this._createCurrentValuesBar();
    this._canvas = this._canvasContainer.createChild("canvas");
    this._canvas.id = "memory-counters-graph";
    this._lastMarkerXPosition = 0;

    this._canvas.addEventListener("mouseover", this._onMouseOver.bind(this), true);
    this._canvas.addEventListener("mousemove", this._onMouseMove.bind(this), true);
    this._canvas.addEventListener("mouseout", this._onMouseOut.bind(this), true);
    this._canvas.addEventListener("click", this._onClick.bind(this), true);
    // We create extra timeline grid here to reuse its event dividers.
    this._timelineGrid = new WebInspector.TimelineGrid();
    this._canvasContainer.appendChild(this._timelineGrid.dividersElement);

    // Populate sidebar
    this._memorySidebarView.sidebarElement.createChild("div", "sidebar-tree sidebar-tree-section").textContent = WebInspector.UIString("COUNTERS");
    this._counterUI = this._createCounterUIList();
}

/**
 * @constructor
 * @param {number} time
 */
WebInspector.MemoryStatistics.Counter = function(time)
{
    this.time = time;
}

/**
 * @constructor
 * @extends {WebInspector.Object}
 */
WebInspector.SwatchCheckbox = function(title, color)
{
    this.element = document.createElement("div");
    this._swatch = this.element.createChild("div", "swatch");
    this.element.createChild("span", "title").textContent = title;
    this._color = color;
    this.checked = true;

    this.element.addEventListener("click", this._toggleCheckbox.bind(this), true);
}

WebInspector.SwatchCheckbox.Events = {
    Changed: "Changed"
}

WebInspector.SwatchCheckbox.prototype = {
    get checked()
    {
        return this._checked;
    },

    set checked(v)
    {
        this._checked = v;
        if (this._checked)
            this._swatch.style.backgroundColor = this._color;
        else
            this._swatch.style.backgroundColor = "";
    },

    _toggleCheckbox: function(event)
    {
        this.checked = !this.checked;
        this.dispatchEventToListeners(WebInspector.SwatchCheckbox.Events.Changed);
    },

    __proto__: WebInspector.Object.prototype
}

/**
 * @constructor
 */
WebInspector.CounterUIBase = function(memoryCountersPane, title, graphColor, valueGetter)
{
    this._memoryCountersPane = memoryCountersPane;
    this.valueGetter = valueGetter;
    var container = memoryCountersPane._memorySidebarView.sidebarElement.createChild("div", "memory-counter-sidebar-info");
    var swatchColor = graphColor;
    this._swatch = new WebInspector.SwatchCheckbox(WebInspector.UIString(title), swatchColor);
    this._swatch.addEventListener(WebInspector.SwatchCheckbox.Events.Changed, this._toggleCounterGraph.bind(this));
    container.appendChild(this._swatch.element);

    this._value = null;
    this.graphColor =graphColor;
    this.strokeColor = graphColor;
    this.graphYValues = [];
}

WebInspector.CounterUIBase.prototype = {
    _toggleCounterGraph: function(event)
    {
        if (this._swatch.checked)
            this._value.removeStyleClass("hidden");
        else
            this._value.addStyleClass("hidden");
        this._memoryCountersPane.refresh();
    },

    updateCurrentValue: function(countersEntry)
    {
        this._value.textContent = Number.bytesToString(this.valueGetter(countersEntry));
    },

    clearCurrentValueAndMarker: function(ctx)
    {
        this._value.textContent = "";
    },

    get visible()
    {
        return this._swatch.checked;
    },
}

WebInspector.MemoryStatistics.prototype = {
    _createCurrentValuesBar: function()
    {
        throw new Error("Not implemented");
    },

    _createCounterUIList: function()
    {
        throw new Error("Not implemented");
    },

    _onRecordsCleared: function()
    {
        this._counters = [];
    },

    /**
     * @param {WebInspector.TimelineGrid} timelineGrid
     */
    setMainTimelineGrid: function(timelineGrid)
    {
        this._mainTimelineGrid = timelineGrid;
    },

    /**
     * @param {number} top
     */
     setTopPosition: function(top)
    {
        this._memorySidebarView.element.style.top = top + "px";
        this._updateSize();
    },

    /**
     * @param {number} width
     */
    setSidebarWidth: function(width)
    {
        if (this._ignoreSidebarResize)
            return;
        this._ignoreSidebarResize = true;
        this._memorySidebarView.setSidebarWidth(width);
        this._ignoreSidebarResize = false;
    },

    /**
     * @param {WebInspector.Event} event
     */
    _sidebarResized: function(event)
    {
        if (this._ignoreSidebarResize)
            return;
        this._ignoreSidebarResize = true;
        this._timelinePanel.splitView.setSidebarWidth(event.data);
        this._ignoreSidebarResize = false;
    },

    _canvasHeight: function()
    {
        throw new Error("Not implemented");
    },

    _updateSize: function()
    {
        var width = this._mainTimelineGrid.dividersElement.offsetWidth + 1;
        this._canvasContainer.style.width = width + "px";

        var height = this._canvasHeight();
        this._canvas.width = width;
        this._canvas.height = height;
    },

    /**
     * @param {WebInspector.Event} event
     */
    _onRecordAdded: function(event)
    {
        throw new Error("Not implemented");
    },

    _draw: function()
    {
        this._calculateVisibleIndexes();
        this._calculateXValues();
        this._clear();

        this._setVerticalClip(10, this._canvas.height - 20);
    },

    _calculateVisibleIndexes: function()
    {
        var calculator = this._timelinePanel.calculator;
        var start = calculator.minimumBoundary() * 1000;
        var end = calculator.maximumBoundary() * 1000;
        function comparator(value, sample)
        {
            return value - sample.time;
        }
        var firstIndex = binarySearch(start, this._counters, comparator);
        var lastIndex = binarySearch(end, this._counters, comparator);
        if (firstIndex < 0)
            firstIndex = -firstIndex - 2;
        if (lastIndex < 0)
            lastIndex = -lastIndex - 1;

        // Maximum index of element whose time <= start.
        this._minimumIndex = firstIndex;

        // Minimum index of element whose time >= end.
        this._maximumIndex = lastIndex;

        // Current window bounds.
        this._minTime = start;
        this._maxTime = end;
    },

    /**
     * @param {MouseEvent} event
     */
     _onClick: function(event)
    {
        var x = event.x - event.target.offsetParent.offsetLeft;
        var i = this._recordIndexAt(x);
        var counter = this._counters[i];
        if (counter)
            this._timelinePanel.revealRecordAt(counter.time / 1000);
    },

    /**
     * @param {MouseEvent} event
     */
     _onMouseOut: function(event)
    {
        delete this._markerXPosition;

        var ctx = this._canvas.getContext("2d");
        this._clearCurrentValueAndMarker(ctx);
    },

    /**
     * @param {CanvasRenderingContext2D} ctx
     */
    _clearCurrentValueAndMarker: function(ctx)
    {
        for (var i = 0; i < this._counterUI.length; i++)
            this._counterUI[i].clearCurrentValueAndMarker(ctx);
    },

    /**
     * @param {MouseEvent} event
     */
     _onMouseOver: function(event)
    {
        this._onMouseMove(event);
    },

    /**
     * @param {MouseEvent} event
     */
     _onMouseMove: function(event)
    {
        var x = event.x - event.target.offsetParent.offsetLeft
        this._markerXPosition = x;
        this._refreshCurrentValues();
    },

    _refreshCurrentValues: function()
    {
        if (!this._counters.length)
            return;
        if (this._markerXPosition === undefined)
            return;
        if (this._maximumIndex === -1)
            return;
        var i = this._recordIndexAt(this._markerXPosition);

        this._updateCurrentValue(this._counters[i]);

        this._highlightCurrentPositionOnGraphs(this._markerXPosition, i);
    },

    _updateCurrentValue: function(counterEntry)
    {
        for (var j = 0; j < this._counterUI.length; j++)
            this._counterUI[j].updateCurrentValue(counterEntry);
    },

    _recordIndexAt: function(x)
    {
        var i;
        for (i = this._minimumIndex + 1; i <= this._maximumIndex; i++) {
            var statX = this._counters[i].x;
            if (x < statX)
                break;
        }
        i--;
        return i;
    },

    _highlightCurrentPositionOnGraphs: function(x, index)
    {
        var ctx = this._canvas.getContext("2d");
        this._restoreImageUnderMarker(ctx);
        this._drawMarker(ctx, x, index);
    },

    _restoreImageUnderMarker: function(ctx)
    {
        throw new Error("Not implemented");
    },

    _drawMarker: function(ctx, x, index)
    {
        throw new Error("Not implemented");
    },

    visible: function()
    {
        return this._memorySidebarView.isShowing();
    },

    show: function()
    {
        var anchor = /** @type {Element|null} */ (this._containerAnchor.nextSibling);
        this._memorySidebarView.show(this._timelinePanel.element, anchor);
        this._updateSize();
        this._refreshDividers();
        setTimeout(this._draw.bind(this), 0);
    },

    refresh: function()
    {
        this._updateSize();
        this._refreshDividers();
        this._draw();
        this._refreshCurrentValues();
    },

    hide: function()
    {
        this._memorySidebarView.detach();
    },

    _refreshDividers: function()
    {
        this._timelineGrid.updateDividers(this._timelinePanel.calculator);
    },

    _setVerticalClip: function(originY, height)
    {
        this._originY = originY;
        this._clippedHeight = height;
    },

    _calculateXValues: function()
    {
        if (!this._counters.length)
            return;

        var width = this._canvas.width;
        var xFactor = width / (this._maxTime - this._minTime);

        this._counters[this._minimumIndex].x = 0;
        for (var i = this._minimumIndex + 1; i < this._maximumIndex; i++)
             this._counters[i].x = xFactor * (this._counters[i].time - this._minTime);
        this._counters[this._maximumIndex].x = width;
    },

    _clear: function() {
        var ctx = this._canvas.getContext("2d");
        ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
        this._discardImageUnderMarker();
    },

    _discardImageUnderMarker: function()
    {
        throw new Error("Not implemented");
    }
}

<commit_msg>DevTools: Constrain DOM counters chart window boundary indices to be within the samples array bounds.<commit_after>/*
 * Copyright (C) 2012 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * @param {WebInspector.TimelinePanel} timelinePanel
 * @param {WebInspector.TimelineModel} model
 * @param {number} sidebarWidth
 * @constructor
 */
WebInspector.MemoryStatistics = function(timelinePanel, model, sidebarWidth)
{
    this._timelinePanel = timelinePanel;
    this._counters = [];

    model.addEventListener(WebInspector.TimelineModel.Events.RecordAdded, this._onRecordAdded, this);
    model.addEventListener(WebInspector.TimelineModel.Events.RecordsCleared, this._onRecordsCleared, this);

    this._containerAnchor = timelinePanel.element.lastChild;
    this._memorySidebarView = new WebInspector.SidebarView(WebInspector.SidebarView.SidebarPosition.Start, undefined, sidebarWidth);
    this._memorySidebarView.sidebarElement.addStyleClass("sidebar");
    this._memorySidebarView.element.id = "memory-graphs-container";

    this._memorySidebarView.addEventListener(WebInspector.SidebarView.EventTypes.Resized, this._sidebarResized.bind(this));

    this._canvasContainer = this._memorySidebarView.mainElement;
    this._canvasContainer.id = "memory-graphs-canvas-container";
    this._createCurrentValuesBar();
    this._canvas = this._canvasContainer.createChild("canvas");
    this._canvas.id = "memory-counters-graph";
    this._lastMarkerXPosition = 0;

    this._canvas.addEventListener("mouseover", this._onMouseOver.bind(this), true);
    this._canvas.addEventListener("mousemove", this._onMouseMove.bind(this), true);
    this._canvas.addEventListener("mouseout", this._onMouseOut.bind(this), true);
    this._canvas.addEventListener("click", this._onClick.bind(this), true);
    // We create extra timeline grid here to reuse its event dividers.
    this._timelineGrid = new WebInspector.TimelineGrid();
    this._canvasContainer.appendChild(this._timelineGrid.dividersElement);

    // Populate sidebar
    this._memorySidebarView.sidebarElement.createChild("div", "sidebar-tree sidebar-tree-section").textContent = WebInspector.UIString("COUNTERS");
    this._counterUI = this._createCounterUIList();
}

/**
 * @constructor
 * @param {number} time
 */
WebInspector.MemoryStatistics.Counter = function(time)
{
    this.time = time;
}

/**
 * @constructor
 * @extends {WebInspector.Object}
 */
WebInspector.SwatchCheckbox = function(title, color)
{
    this.element = document.createElement("div");
    this._swatch = this.element.createChild("div", "swatch");
    this.element.createChild("span", "title").textContent = title;
    this._color = color;
    this.checked = true;

    this.element.addEventListener("click", this._toggleCheckbox.bind(this), true);
}

WebInspector.SwatchCheckbox.Events = {
    Changed: "Changed"
}

WebInspector.SwatchCheckbox.prototype = {
    get checked()
    {
        return this._checked;
    },

    set checked(v)
    {
        this._checked = v;
        if (this._checked)
            this._swatch.style.backgroundColor = this._color;
        else
            this._swatch.style.backgroundColor = "";
    },

    _toggleCheckbox: function(event)
    {
        this.checked = !this.checked;
        this.dispatchEventToListeners(WebInspector.SwatchCheckbox.Events.Changed);
    },

    __proto__: WebInspector.Object.prototype
}

/**
 * @constructor
 */
WebInspector.CounterUIBase = function(memoryCountersPane, title, graphColor, valueGetter)
{
    this._memoryCountersPane = memoryCountersPane;
    this.valueGetter = valueGetter;
    var container = memoryCountersPane._memorySidebarView.sidebarElement.createChild("div", "memory-counter-sidebar-info");
    var swatchColor = graphColor;
    this._swatch = new WebInspector.SwatchCheckbox(WebInspector.UIString(title), swatchColor);
    this._swatch.addEventListener(WebInspector.SwatchCheckbox.Events.Changed, this._toggleCounterGraph.bind(this));
    container.appendChild(this._swatch.element);

    this._value = null;
    this.graphColor =graphColor;
    this.strokeColor = graphColor;
    this.graphYValues = [];
}

WebInspector.CounterUIBase.prototype = {
    _toggleCounterGraph: function(event)
    {
        if (this._swatch.checked)
            this._value.removeStyleClass("hidden");
        else
            this._value.addStyleClass("hidden");
        this._memoryCountersPane.refresh();
    },

    updateCurrentValue: function(countersEntry)
    {
        this._value.textContent = Number.bytesToString(this.valueGetter(countersEntry));
    },

    clearCurrentValueAndMarker: function(ctx)
    {
        this._value.textContent = "";
    },

    get visible()
    {
        return this._swatch.checked;
    },
}

WebInspector.MemoryStatistics.prototype = {
    _createCurrentValuesBar: function()
    {
        throw new Error("Not implemented");
    },

    _createCounterUIList: function()
    {
        throw new Error("Not implemented");
    },

    _onRecordsCleared: function()
    {
        this._counters = [];
    },

    /**
     * @param {WebInspector.TimelineGrid} timelineGrid
     */
    setMainTimelineGrid: function(timelineGrid)
    {
        this._mainTimelineGrid = timelineGrid;
    },

    /**
     * @param {number} top
     */
     setTopPosition: function(top)
    {
        this._memorySidebarView.element.style.top = top + "px";
        this._updateSize();
    },

    /**
     * @param {number} width
     */
    setSidebarWidth: function(width)
    {
        if (this._ignoreSidebarResize)
            return;
        this._ignoreSidebarResize = true;
        this._memorySidebarView.setSidebarWidth(width);
        this._ignoreSidebarResize = false;
    },

    /**
     * @param {WebInspector.Event} event
     */
    _sidebarResized: function(event)
    {
        if (this._ignoreSidebarResize)
            return;
        this._ignoreSidebarResize = true;
        this._timelinePanel.splitView.setSidebarWidth(event.data);
        this._ignoreSidebarResize = false;
    },

    _canvasHeight: function()
    {
        throw new Error("Not implemented");
    },

    _updateSize: function()
    {
        var width = this._mainTimelineGrid.dividersElement.offsetWidth + 1;
        this._canvasContainer.style.width = width + "px";

        var height = this._canvasHeight();
        this._canvas.width = width;
        this._canvas.height = height;
    },

    /**
     * @param {WebInspector.Event} event
     */
    _onRecordAdded: function(event)
    {
        throw new Error("Not implemented");
    },

    _draw: function()
    {
        this._calculateVisibleIndexes();
        this._calculateXValues();
        this._clear();

        this._setVerticalClip(10, this._canvas.height - 20);
    },

    _calculateVisibleIndexes: function()
    {
        var calculator = this._timelinePanel.calculator;
        var start = calculator.minimumBoundary() * 1000;
        var end = calculator.maximumBoundary() * 1000;
        function comparator(value, sample)
        {
            return value - sample.time;
        }
        var firstIndex = binarySearch(start, this._counters, comparator);
        var lastIndex = binarySearch(end, this._counters, comparator);
        if (firstIndex < 0)
            firstIndex = Math.max(0, -firstIndex - 2);
        if (lastIndex < 0)
            lastIndex = Math.min(-lastIndex - 1, this._counters.length - 1);

        // Maximum index of element whose time <= start.
        this._minimumIndex = firstIndex;

        // Minimum index of element whose time >= end.
        this._maximumIndex = lastIndex;

        // Current window bounds.
        this._minTime = start;
        this._maxTime = end;
    },

    /**
     * @param {MouseEvent} event
     */
     _onClick: function(event)
    {
        var x = event.x - event.target.offsetParent.offsetLeft;
        var i = this._recordIndexAt(x);
        var counter = this._counters[i];
        if (counter)
            this._timelinePanel.revealRecordAt(counter.time / 1000);
    },

    /**
     * @param {MouseEvent} event
     */
     _onMouseOut: function(event)
    {
        delete this._markerXPosition;

        var ctx = this._canvas.getContext("2d");
        this._clearCurrentValueAndMarker(ctx);
    },

    /**
     * @param {CanvasRenderingContext2D} ctx
     */
    _clearCurrentValueAndMarker: function(ctx)
    {
        for (var i = 0; i < this._counterUI.length; i++)
            this._counterUI[i].clearCurrentValueAndMarker(ctx);
    },

    /**
     * @param {MouseEvent} event
     */
     _onMouseOver: function(event)
    {
        this._onMouseMove(event);
    },

    /**
     * @param {MouseEvent} event
     */
     _onMouseMove: function(event)
    {
        var x = event.x - event.target.offsetParent.offsetLeft
        this._markerXPosition = x;
        this._refreshCurrentValues();
    },

    _refreshCurrentValues: function()
    {
        if (!this._counters.length)
            return;
        if (this._markerXPosition === undefined)
            return;
        if (this._maximumIndex === -1)
            return;
        var i = this._recordIndexAt(this._markerXPosition);

        this._updateCurrentValue(this._counters[i]);

        this._highlightCurrentPositionOnGraphs(this._markerXPosition, i);
    },

    _updateCurrentValue: function(counterEntry)
    {
        for (var j = 0; j < this._counterUI.length; j++)
            this._counterUI[j].updateCurrentValue(counterEntry);
    },

    _recordIndexAt: function(x)
    {
        var i;
        for (i = this._minimumIndex + 1; i <= this._maximumIndex; i++) {
            var statX = this._counters[i].x;
            if (x < statX)
                break;
        }
        i--;
        return i;
    },

    _highlightCurrentPositionOnGraphs: function(x, index)
    {
        var ctx = this._canvas.getContext("2d");
        this._restoreImageUnderMarker(ctx);
        this._drawMarker(ctx, x, index);
    },

    _restoreImageUnderMarker: function(ctx)
    {
        throw new Error("Not implemented");
    },

    _drawMarker: function(ctx, x, index)
    {
        throw new Error("Not implemented");
    },

    visible: function()
    {
        return this._memorySidebarView.isShowing();
    },

    show: function()
    {
        var anchor = /** @type {Element|null} */ (this._containerAnchor.nextSibling);
        this._memorySidebarView.show(this._timelinePanel.element, anchor);
        this._updateSize();
        this._refreshDividers();
        setTimeout(this._draw.bind(this), 0);
    },

    refresh: function()
    {
        this._updateSize();
        this._refreshDividers();
        this._draw();
        this._refreshCurrentValues();
    },

    hide: function()
    {
        this._memorySidebarView.detach();
    },

    _refreshDividers: function()
    {
        this._timelineGrid.updateDividers(this._timelinePanel.calculator);
    },

    _setVerticalClip: function(originY, height)
    {
        this._originY = originY;
        this._clippedHeight = height;
    },

    _calculateXValues: function()
    {
        if (!this._counters.length)
            return;

        var width = this._canvas.width;
        var xFactor = width / (this._maxTime - this._minTime);

        this._counters[this._minimumIndex].x = 0;
        for (var i = this._minimumIndex + 1; i < this._maximumIndex; i++)
             this._counters[i].x = xFactor * (this._counters[i].time - this._minTime);
        this._counters[this._maximumIndex].x = width;
    },

    _clear: function() {
        var ctx = this._canvas.getContext("2d");
        ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
        this._discardImageUnderMarker();
    },

    _discardImageUnderMarker: function()
    {
        throw new Error("Not implemented");
    }
}

<|endoftext|>