+check.wit.com
+
+
+
+
+
diff --git a/chrome/action_link.css b/chrome/action_link.css
new file mode 100644
index 0000000..c645cf1
--- /dev/null
+++ b/chrome/action_link.css
@@ -0,0 +1,29 @@
+/* Copyright 2015 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+[is='action-link'] {
+ cursor: pointer;
+ display: inline-block;
+ text-decoration: none;
+}
+
+[is='action-link']:hover {
+ text-decoration: underline;
+}
+
+[is='action-link']:active {
+ color: rgb(5, 37, 119);
+ text-decoration: underline;
+}
+
+[is='action-link'][disabled] {
+ color: #999;
+ cursor: default;
+ pointer-events: none;
+ text-decoration: none;
+}
+
+[is='action-link'].no-outline {
+ outline: none;
+}
diff --git a/chrome/browser_bridge_tests.js b/chrome/browser_bridge_tests.js
new file mode 100644
index 0000000..52ee2f7
--- /dev/null
+++ b/chrome/browser_bridge_tests.js
@@ -0,0 +1,346 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+var commandLineFlags = ['--flag-switches-begin',
+ '--show-composited-layer-borders',
+ '--show-fps-counter',
+ '--flag-switches-end'];
+var commandLineStr = './out/Debug/chrome ' + commandLineFlags.join(' ');
+
+var glValueArray = ['GL_ARB_compatibility',
+ 'GL_ARB_copy_buffer',
+ 'GL_ARB_depth_buffer_float',
+ 'GL_ARB_depth_clamp',
+ 'GL_ARB_depth_texture',
+ 'GL_ARB_draw_buffers',
+ 'GL_ARB_draw_elements_base_vertex',
+ 'GL_ARB_draw_instanced',
+ 'GL_ARB_fragment_coord_conventions',
+ 'GL_ARB_fragment_program',
+ 'GL_ARB_fragment_program_shadow',
+ 'GL_ARB_fragment_shader',
+ 'GL_ARB_framebuffer_object',
+ 'GL_ARB_framebuffer_sRGB',
+ 'GL_ARB_geometry_shader4',
+ 'GL_ARB_half_float_pixel',
+ 'GL_ARB_half_float_vertex',
+ 'GL_ARB_imaging',
+ 'GL_ARB_map_buffer_range',
+ 'GL_ARB_multisample',
+ 'GL_ARB_multitexture',
+ 'GL_ARB_occlusion_query',
+ 'GL_ARB_pixel_buffer_object',
+ 'GL_ARB_point_parameters',
+ 'GL_ARB_point_sprite',
+ 'GL_ARB_provoking_vertex',
+ 'GL_ARB_seamless_cube_map',
+ 'GL_ARB_shader_objects',
+ 'GL_ARB_shading_language_100',
+ 'GL_ARB_shadow',
+ 'GL_ARB_sync',
+ 'GL_ARB_texture_border_clamp',
+ 'GL_ARB_texture_buffer_object',
+ 'GL_ARB_texture_compression',
+ 'GL_ARB_texture_compression_rgtc',
+ 'GL_ARB_texture_cube_map',
+ 'GL_ARB_texture_env_add',
+ 'GL_ARB_texture_env_combine',
+ 'GL_ARB_texture_env_crossbar',
+ 'GL_ARB_texture_env_dot3',
+ 'GL_ARB_texture_float',
+ 'GL_ARB_texture_mirrored_repeat',
+ 'GL_ARB_texture_multisample',
+ 'GL_ARB_texture_non_power_of_two',
+ 'GL_ARB_texture_rectangle',
+ 'GL_ARB_texture_rg',
+ 'GL_ARB_transpose_matrix',
+ 'GL_ARB_uniform_buffer_object',
+ 'GL_ARB_vertex_array_bgra',
+ 'GL_ARB_vertex_array_object',
+ 'GL_ARB_vertex_buffer_object',
+ 'GL_ARB_vertex_program',
+ 'GL_ARB_vertex_shader',
+ 'GL_ARB_window_pos',
+ 'GL_ATI_draw_buffers',
+ 'GL_ATI_texture_float',
+ 'GL_ATI_texture_mirror_once',
+ 'GL_S3_s3tc',
+ 'GL_EXT_texture_env_add',
+ 'GL_EXT_abgr',
+ 'GL_EXT_bgra',
+ 'GL_EXT_bindable_uniform',
+ 'GL_EXT_blend_color',
+ 'GL_EXT_blend_equation_separate',
+ 'GL_EXT_blend_func_separate',
+ 'GL_EXT_blend_minmax',
+ 'GL_EXT_blend_subtract',
+ 'GL_EXT_compiled_vertex_array',
+ 'GL_EXT_Cg_shader',
+ 'GL_EXT_depth_bounds_test',
+ 'GL_EXT_direct_state_access',
+ 'GL_EXT_draw_buffers2',
+ 'GL_EXT_draw_instanced',
+ 'GL_EXT_draw_range_elements',
+ 'GL_EXT_fog_coord',
+ 'GL_EXT_framebuffer_blit',
+ 'GL_EXT_framebuffer_multisample',
+ 'GL_EXTX_framebuffer_mixed_formats',
+ 'GL_EXT_framebuffer_object',
+ 'GL_EXT_framebuffer_sRGB',
+ 'GL_EXT_geometry_shader4',
+ 'GL_EXT_gpu_program_parameters',
+ 'GL_EXT_gpu_shader4',
+ 'GL_EXT_multi_draw_arrays',
+ 'GL_EXT_packed_depth_stencil',
+ 'GL_EXT_packed_float',
+ 'GL_EXT_packed_pixels',
+ 'GL_EXT_pixel_buffer_object',
+ 'GL_EXT_point_parameters',
+ 'GL_EXT_provoking_vertex',
+ 'GL_EXT_rescale_normal',
+ 'GL_EXT_secondary_color',
+ 'GL_EXT_separate_shader_objects',
+ 'GL_EXT_separate_specular_color',
+ 'GL_EXT_shadow_funcs',
+ 'GL_EXT_stencil_two_side',
+ 'GL_EXT_stencil_wrap',
+ 'GL_EXT_texture3D',
+ 'GL_EXT_texture_array',
+ 'GL_EXT_texture_buffer_object',
+ 'GL_EXT_texture_compression_latc',
+ 'GL_EXT_texture_compression_rgtc',
+ 'GL_EXT_texture_compression_s3tc',
+ 'GL_EXT_texture_cube_map',
+ 'GL_EXT_texture_edge_clamp',
+ 'GL_EXT_texture_env_combine',
+ 'GL_EXT_texture_env_dot3',
+ 'GL_EXT_texture_filter_anisotropic',
+ 'GL_EXT_texture_integer',
+ 'GL_EXT_texture_lod',
+ 'GL_EXT_texture_lod_bias',
+ 'GL_EXT_texture_mirror_clamp',
+ 'GL_EXT_texture_object',
+ 'GL_EXT_texture_shared_exponent',
+ 'GL_EXT_texture_sRGB',
+ 'GL_EXT_texture_swizzle',
+ 'GL_EXT_timer_query',
+ 'GL_EXT_vertex_array',
+ 'GL_EXT_vertex_array_bgra',
+ 'GL_IBM_rasterpos_clip',
+ 'GL_IBM_texture_mirrored_repeat',
+ 'GL_KTX_buffer_region',
+ 'GL_NV_blend_square',
+ 'GL_NV_conditional_render',
+ 'GL_NV_copy_depth_to_color',
+ 'GL_NV_copy_image',
+ 'GL_NV_depth_buffer_float',
+ 'GL_NV_depth_clamp',
+ 'GL_NV_explicit_multisample',
+ 'GL_NV_fence',
+ 'GL_NV_float_buffer',
+ 'GL_NV_fog_distance',
+ 'GL_NV_fragment_program',
+ 'GL_NV_fragment_program_option',
+ 'GL_NV_fragment_program2',
+ 'GL_NV_framebuffer_multisample_coverage',
+ 'GL_NV_geometry_shader4',
+ 'GL_NV_gpu_program4',
+ 'GL_NV_half_float',
+ 'GL_NV_light_max_exponent',
+ 'GL_NV_multisample_coverage',
+ 'GL_NV_multisample_filter_hint',
+ 'GL_NV_occlusion_query',
+ 'GL_NV_packed_depth_stencil',
+ 'GL_NV_parameter_buffer_object',
+ 'GL_NV_parameter_buffer_object2',
+ 'GL_NV_pixel_data_range',
+ 'GL_NV_point_sprite',
+ 'GL_NV_primitive_restart',
+ 'GL_NV_register_combiners',
+ 'GL_NV_register_combiners2',
+ 'GL_NV_shader_buffer_load',
+ 'GL_NV_texgen_reflection',
+ 'GL_NV_texture_barrier',
+ 'GL_NV_texture_compression_vtc',
+ 'GL_NV_texture_env_combine4',
+ 'GL_NV_texture_expand_normal',
+ 'GL_NV_texture_rectangle',
+ 'GL_NV_texture_shader',
+ 'GL_NV_texture_shader2',
+ 'GL_NV_texture_shader3',
+ 'GL_NV_transform_feedback',
+ 'GL_NV_vertex_array_range',
+ 'GL_NV_vertex_array_range2',
+ 'GL_NV_vertex_buffer_unified_memory',
+ 'GL_NV_vertex_program',
+ 'GL_NV_vertex_program1_1',
+ 'GL_NV_vertex_program2',
+ 'GL_NV_vertex_program2_option',
+ 'GL_NV_vertex_program3',
+ 'GL_NVX_conditional_render',
+ 'GL_NVX_gpu_memory_info',
+ 'GL_SGIS_generate_mipmap',
+ 'GL_SGIS_texture_lod',
+ 'GL_SGIX_depth_texture',
+ 'GL_SGIX_shadow',
+ 'GL_SUN_slice_accum'];
+(function() {
+ var dataSets = [
+ {
+ name: 'full_data_linux',
+ gpuInfo: {
+ basic_info: [
+ {
+ description: 'Initialization time',
+ value: '111'
+ },
+ {
+ description: 'Vendor Id',
+ value: '0x10de'
+ },
+ {
+ description: 'Device Id',
+ value: '0x0658'
+ },
+ {
+ description: 'Driver vendor',
+ value: 'NVIDIA'
+ },
+ {
+ description: 'Driver version',
+ value: '195.36.24'
+ },
+ {
+ description: 'Driver date',
+ value: ''
+ },
+ {
+ description: 'Pixel shader version',
+ value: '1.50'
+ },
+ {
+ description: 'Vertex shader version',
+ value: '1.50'
+ },
+ {
+ description: 'GL version',
+ value: '3.2'
+ },
+ {
+ description: 'GL_VENDOR',
+ value: 'NVIDIA Corporation'
+ },
+ {
+ description: 'GL_RENDERER',
+ value: 'Quadro FX 380/PCI/SSE2'
+ },
+ {
+ description: 'GL_VERSION',
+ value: '3.2.0 NVIDIA 195.36.24'
+ },
+ {
+ description: 'GL_EXTENSIONS',
+ value: glValueArray.join(' '),
+ }
+ ],
+ featureStatus: {
+ featureStatus:
+ [
+ {'status': 'enabled', name: '2d_canvas'},
+ {'status': 'enabled', name: '3d_css'},
+ {'status': 'enabled', name: 'compositing'},
+ {'status': 'enabled', name: 'webgl'},
+ {'status': 'enabled', name: 'multisampling'}
+ ],
+ problems: []
+ }
+ },
+ clientInfo: {
+ blacklist_version: '1.10',
+ command_line: commandLineStr,
+ version: 'Chrome/12.0.729.0',
+ },
+ logMessages: []
+ },
+ {
+ name: 'no_data',
+ gpuInfo: undefined,
+ clientInfo: undefined,
+ logMessages: undefined
+ },
+ {
+ name: 'logs',
+ gpuInfo: undefined,
+ clientInfo: undefined,
+ logMessages: [
+ {header: 'foo', message: 'Bar'}
+ ]
+ },
+
+ // tests for 'status'
+ {
+ name: 'feature_states',
+ gpuInfo: {
+ basic_info: undefined,
+ featureStatus: {
+ featureStatus: [
+ {'status': 'disabled_off', name: '2d_canvas'},
+ {'status': 'unavailable_software', name: '3d_css'},
+ {'status': 'disabled_software', name: 'compositing'},
+ {'status': 'software', name: 'compositing'},
+ {'status': 'unavailable_off', name: 'webgl'},
+ {'status': 'enabled', name: 'multisampling'}
+ ],
+ problems: [
+ {
+ description: 'Something wrong',
+ crBugs: [],
+ webkitBugs: []
+ },
+ {
+ description: 'SomethingElse',
+ crBugs: [],
+ webkitBugs: []
+ },
+ {
+ description: 'WebKit and Chrome bug',
+ crBugs: [23456],
+ webkitBugs: [789, 2123]
+ }
+ ]
+ }
+ },
+ clientInfo: undefined,
+ logMessages: []
+ }
+
+ ];
+
+ var selectEl = document.createElement('select');
+ for (var i = 0; i < dataSets.length; ++i) {
+ var optionEl = document.createElement('option');
+ optionEl.textContent = dataSets[i].name;
+ optionEl.dataSet = dataSets[i];
+ selectEl.add(optionEl);
+ }
+ selectEl.addEventListener('change', function() {
+ browserBridge.applySimulatedData_(dataSets[selectEl.selectedIndex]);
+ });
+ selectEl.addEventListener('keydown', function() {
+ window.setTimeout(function() {
+ browserBridge.applySimulatedData_(dataSets[selectEl.selectedIndex]);
+ }, 0);
+ });
+
+ var controlEl = document.createElement('div');
+ var textEl = document.createElement('span');
+ textEl.textContent = 'GPU Info:';
+ controlEl.appendChild(textEl);
+ controlEl.appendChild(selectEl);
+
+ // document.querySelector('#debug-div').appendChild(controlEl, document.body.firstChild);
+ console.log("hello from browser stuff.js");
+
+ browserBridge.applySimulatedData_(dataSets[0]);
+
+ })();
diff --git a/chrome/cr.js b/chrome/cr.js
new file mode 100644
index 0000000..75a4e86
--- /dev/null
+++ b/chrome/cr.js
@@ -0,0 +1,489 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * The global object.
+ * @type {!Object}
+ * @const
+ */
+var global = this;
+
+/** @typedef {{eventName: string, uid: number}} */
+var WebUIListener;
+
+/** Platform, package, object property, and Event support. **/
+var cr = cr || function() {
+ 'use strict';
+
+ /**
+ * Builds an object structure for the provided namespace path,
+ * ensuring that names that already exist are not overwritten. For
+ * example:
+ * "a.b.c" -> a = {};a.b={};a.b.c={};
+ * @param {string} name Name of the object that this file defines.
+ * @param {*=} opt_object The object to expose at the end of the path.
+ * @param {Object=} opt_objectToExportTo The object to add the path to;
+ * default is {@code global}.
+ * @return {!Object} The last object exported (i.e. exportPath('cr.ui')
+ * returns a reference to the ui property of window.cr).
+ * @private
+ */
+ function exportPath(name, opt_object, opt_objectToExportTo) {
+ var parts = name.split('.');
+ var cur = opt_objectToExportTo || global;
+
+ for (var part; parts.length && (part = parts.shift());) {
+ if (!parts.length && opt_object !== undefined) {
+ // last part and we have an object; use it
+ cur[part] = opt_object;
+ } else if (part in cur) {
+ cur = cur[part];
+ } else {
+ cur = cur[part] = {};
+ }
+ }
+ return cur;
+ }
+
+ /**
+ * Fires a property change event on the target.
+ * @param {EventTarget} target The target to dispatch the event on.
+ * @param {string} propertyName The name of the property that changed.
+ * @param {*} newValue The new value for the property.
+ * @param {*} oldValue The old value for the property.
+ */
+ function dispatchPropertyChange(target, propertyName, newValue, oldValue) {
+ var e = new Event(propertyName + 'Change');
+ e.propertyName = propertyName;
+ e.newValue = newValue;
+ e.oldValue = oldValue;
+ target.dispatchEvent(e);
+ }
+
+ /**
+ * Converts a camelCase javascript property name to a hyphenated-lower-case
+ * attribute name.
+ * @param {string} jsName The javascript camelCase property name.
+ * @return {string} The equivalent hyphenated-lower-case attribute name.
+ */
+ function getAttributeName(jsName) {
+ return jsName.replace(/([A-Z])/g, '-$1').toLowerCase();
+ }
+
+ /**
+ * The kind of property to define in {@code defineProperty}.
+ * @enum {string}
+ * @const
+ */
+ var PropertyKind = {
+ /**
+ * Plain old JS property where the backing data is stored as a "private"
+ * field on the object.
+ * Use for properties of any type. Type will not be checked.
+ */
+ JS: 'js',
+
+ /**
+ * The property backing data is stored as an attribute on an element.
+ * Use only for properties of type {string}.
+ */
+ ATTR: 'attr',
+
+ /**
+ * The property backing data is stored as an attribute on an element. If the
+ * element has the attribute then the value is true.
+ * Use only for properties of type {boolean}.
+ */
+ BOOL_ATTR: 'boolAttr'
+ };
+
+ /**
+ * Helper function for defineProperty that returns the getter to use for the
+ * property.
+ * @param {string} name The name of the property.
+ * @param {PropertyKind} kind The kind of the property.
+ * @return {function():*} The getter for the property.
+ */
+ function getGetter(name, kind) {
+ switch (kind) {
+ case PropertyKind.JS:
+ var privateName = name + '_';
+ return function() {
+ return this[privateName];
+ };
+ case PropertyKind.ATTR:
+ var attributeName = getAttributeName(name);
+ return function() {
+ return this.getAttribute(attributeName);
+ };
+ case PropertyKind.BOOL_ATTR:
+ var attributeName = getAttributeName(name);
+ return function() {
+ return this.hasAttribute(attributeName);
+ };
+ }
+
+ // TODO(dbeam): replace with assertNotReached() in assert.js when I can coax
+ // the browser/unit tests to preprocess this file through grit.
+ throw 'not reached';
+ }
+
+ /**
+ * Helper function for defineProperty that returns the setter of the right
+ * kind.
+ * @param {string} name The name of the property we are defining the setter
+ * for.
+ * @param {PropertyKind} kind The kind of property we are getting the
+ * setter for.
+ * @param {function(*, *):void=} opt_setHook A function to run after the
+ * property is set, but before the propertyChange event is fired.
+ * @return {function(*):void} The function to use as a setter.
+ */
+ function getSetter(name, kind, opt_setHook) {
+ switch (kind) {
+ case PropertyKind.JS:
+ var privateName = name + '_';
+ return function(value) {
+ var oldValue = this[name];
+ if (value !== oldValue) {
+ this[privateName] = value;
+ if (opt_setHook)
+ opt_setHook.call(this, value, oldValue);
+ dispatchPropertyChange(this, name, value, oldValue);
+ }
+ };
+
+ case PropertyKind.ATTR:
+ var attributeName = getAttributeName(name);
+ return function(value) {
+ var oldValue = this[name];
+ if (value !== oldValue) {
+ if (value == undefined)
+ this.removeAttribute(attributeName);
+ else
+ this.setAttribute(attributeName, value);
+ if (opt_setHook)
+ opt_setHook.call(this, value, oldValue);
+ dispatchPropertyChange(this, name, value, oldValue);
+ }
+ };
+
+ case PropertyKind.BOOL_ATTR:
+ var attributeName = getAttributeName(name);
+ return function(value) {
+ var oldValue = this[name];
+ if (value !== oldValue) {
+ if (value)
+ this.setAttribute(attributeName, name);
+ else
+ this.removeAttribute(attributeName);
+ if (opt_setHook)
+ opt_setHook.call(this, value, oldValue);
+ dispatchPropertyChange(this, name, value, oldValue);
+ }
+ };
+ }
+
+ // TODO(dbeam): replace with assertNotReached() in assert.js when I can coax
+ // the browser/unit tests to preprocess this file through grit.
+ throw 'not reached';
+ }
+
+ /**
+ * Defines a property on an object. When the setter changes the value a
+ * property change event with the type {@code name + 'Change'} is fired.
+ * @param {!Object} obj The object to define the property for.
+ * @param {string} name The name of the property.
+ * @param {PropertyKind=} opt_kind What kind of underlying storage to use.
+ * @param {function(*, *):void=} opt_setHook A function to run after the
+ * property is set, but before the propertyChange event is fired.
+ */
+ function defineProperty(obj, name, opt_kind, opt_setHook) {
+ if (typeof obj == 'function')
+ obj = obj.prototype;
+
+ var kind = /** @type {PropertyKind} */ (opt_kind || PropertyKind.JS);
+
+ if (!obj.__lookupGetter__(name))
+ obj.__defineGetter__(name, getGetter(name, kind));
+
+ if (!obj.__lookupSetter__(name))
+ obj.__defineSetter__(name, getSetter(name, kind, opt_setHook));
+ }
+
+ /**
+ * Counter for use with createUid
+ */
+ var uidCounter = 1;
+
+ /**
+ * @return {number} A new unique ID.
+ */
+ function createUid() {
+ return uidCounter++;
+ }
+
+ /**
+ * Returns a unique ID for the item. This mutates the item so it needs to be
+ * an object
+ * @param {!Object} item The item to get the unique ID for.
+ * @return {number} The unique ID for the item.
+ */
+ function getUid(item) {
+ if (item.hasOwnProperty('uid'))
+ return item.uid;
+ return item.uid = createUid();
+ }
+
+ /**
+ * Dispatches a simple event on an event target.
+ * @param {!EventTarget} target The event target to dispatch the event on.
+ * @param {string} type The type of the event.
+ * @param {boolean=} opt_bubbles Whether the event bubbles or not.
+ * @param {boolean=} opt_cancelable Whether the default action of the event
+ * can be prevented. Default is true.
+ * @return {boolean} If any of the listeners called {@code preventDefault}
+ * during the dispatch this will return false.
+ */
+ function dispatchSimpleEvent(target, type, opt_bubbles, opt_cancelable) {
+ var e = new Event(type, {
+ bubbles: opt_bubbles,
+ cancelable: opt_cancelable === undefined || opt_cancelable
+ });
+ return target.dispatchEvent(e);
+ }
+
+ /**
+ * Calls |fun| and adds all the fields of the returned object to the object
+ * named by |name|. For example, cr.define('cr.ui', function() {
+ * function List() {
+ * ...
+ * }
+ * function ListItem() {
+ * ...
+ * }
+ * return {
+ * List: List,
+ * ListItem: ListItem,
+ * };
+ * });
+ * defines the functions cr.ui.List and cr.ui.ListItem.
+ * @param {string} name The name of the object that we are adding fields to.
+ * @param {!Function} fun The function that will return an object containing
+ * the names and values of the new fields.
+ */
+ function define(name, fun) {
+ var obj = exportPath(name);
+ var exports = fun();
+ for (var propertyName in exports) {
+ // Maybe we should check the prototype chain here? The current usage
+ // pattern is always using an object literal so we only care about own
+ // properties.
+ var propertyDescriptor =
+ Object.getOwnPropertyDescriptor(exports, propertyName);
+ if (propertyDescriptor)
+ Object.defineProperty(obj, propertyName, propertyDescriptor);
+ }
+ }
+
+ /**
+ * Adds a {@code getInstance} static method that always return the same
+ * instance object.
+ * @param {!Function} ctor The constructor for the class to add the static
+ * method to.
+ */
+ function addSingletonGetter(ctor) {
+ ctor.getInstance = function() {
+ return ctor.instance_ || (ctor.instance_ = new ctor());
+ };
+ }
+
+ /**
+ * Forwards public APIs to private implementations.
+ * @param {Function} ctor Constructor that have private implementations in its
+ * prototype.
+ * @param {Array} methods List of public method names that have their
+ * underscored counterparts in constructor's prototype.
+ * @param {string=} opt_target Selector for target node.
+ */
+ function makePublic(ctor, methods, opt_target) {
+ methods.forEach(function(method) {
+ ctor[method] = function() {
+ var target = opt_target ?
+ // Disable document.getElementById restriction since cr.js should
+ // not depend on util.js.
+ // eslint-disable-next-line no-restricted-properties
+ document.getElementById(opt_target) :
+ ctor.getInstance();
+ return target[method + '_'].apply(target, arguments);
+ };
+ });
+ }
+
+ /**
+ * The mapping used by the sendWithPromise mechanism to tie the Promise
+ * returned to callers with the corresponding WebUI response. The mapping is
+ * from ID to the PromiseResolver helper; the ID is generated by
+ * sendWithPromise and is unique across all invocations of said method.
+ * @type {!Object}
+ */
+ var chromeSendResolverMap = {};
+
+ /**
+ * The named method the WebUI handler calls directly in response to a
+ * chrome.send call that expects a response. The handler requires no knowledge
+ * of the specific name of this method, as the name is passed to the handler
+ * as the first argument in the arguments list of chrome.send. The handler
+ * must pass the ID, also sent via the chrome.send arguments list, as the
+ * first argument of the JS invocation; additionally, the handler may
+ * supply any number of other arguments that will be included in the response.
+ * @param {string} id The unique ID identifying the Promise this response is
+ * tied to.
+ * @param {boolean} isSuccess Whether the request was successful.
+ * @param {*} response The response as sent from C++.
+ */
+ function webUIResponse(id, isSuccess, response) {
+ var resolver = chromeSendResolverMap[id];
+ delete chromeSendResolverMap[id];
+
+ if (isSuccess)
+ resolver.resolve(response);
+ else
+ resolver.reject(response);
+ }
+
+ /**
+ * A variation of chrome.send, suitable for messages that expect a single
+ * response from C++.
+ * @param {string} methodName The name of the WebUI handler API.
+ * @param {...*} var_args Variable number of arguments to be forwarded to the
+ * C++ call.
+ * @return {!Promise}
+ */
+ function sendWithPromise(methodName, var_args) {
+ var args = Array.prototype.slice.call(arguments, 1);
+ var promiseResolver = new PromiseResolver();
+ var id = methodName + '_' + createUid();
+ chromeSendResolverMap[id] = promiseResolver;
+ chrome.send(methodName, [id].concat(args));
+ return promiseResolver.promise;
+ }
+
+ /**
+ * A map of maps associating event names with listeners. The 2nd level map
+ * associates a listener ID with the callback function, such that individual
+ * listeners can be removed from an event without affecting other listeners of
+ * the same event.
+ * @type {!Object>}
+ */
+ var webUIListenerMap = {};
+
+ /**
+ * The named method the WebUI handler calls directly when an event occurs.
+ * The WebUI handler must supply the name of the event as the first argument
+ * of the JS invocation; additionally, the handler may supply any number of
+ * other arguments that will be forwarded to the listener callbacks.
+ * @param {string} event The name of the event that has occurred.
+ * @param {...*} var_args Additional arguments passed from C++.
+ */
+ function webUIListenerCallback(event, var_args) {
+ var eventListenersMap = webUIListenerMap[event];
+ if (!eventListenersMap) {
+ // C++ event sent for an event that has no listeners.
+ // TODO(dpapad): Should a warning be displayed here?
+ return;
+ }
+
+ var args = Array.prototype.slice.call(arguments, 1);
+ for (var listenerId in eventListenersMap) {
+ eventListenersMap[listenerId].apply(null, args);
+ }
+ }
+
+ /**
+ * Registers a listener for an event fired from WebUI handlers. Any number of
+ * listeners may register for a single event.
+ * @param {string} eventName The event to listen to.
+ * @param {!Function} callback The callback run when the event is fired.
+ * @return {!WebUIListener} An object to be used for removing a listener via
+ * cr.removeWebUIListener. Should be treated as read-only.
+ */
+ function addWebUIListener(eventName, callback) {
+ webUIListenerMap[eventName] = webUIListenerMap[eventName] || {};
+ var uid = createUid();
+ webUIListenerMap[eventName][uid] = callback;
+ return {eventName: eventName, uid: uid};
+ }
+
+ /**
+ * Removes a listener. Does nothing if the specified listener is not found.
+ * @param {!WebUIListener} listener The listener to be removed (as returned by
+ * addWebUIListener).
+ * @return {boolean} Whether the given listener was found and actually
+ * removed.
+ */
+ function removeWebUIListener(listener) {
+ var listenerExists = webUIListenerMap[listener.eventName] &&
+ webUIListenerMap[listener.eventName][listener.uid];
+ if (listenerExists) {
+ delete webUIListenerMap[listener.eventName][listener.uid];
+ return true;
+ }
+ return false;
+ }
+
+ return {
+ addSingletonGetter: addSingletonGetter,
+ createUid: createUid,
+ define: define,
+ defineProperty: defineProperty,
+ dispatchPropertyChange: dispatchPropertyChange,
+ dispatchSimpleEvent: dispatchSimpleEvent,
+ exportPath: exportPath,
+ getUid: getUid,
+ makePublic: makePublic,
+ PropertyKind: PropertyKind,
+
+ // C++ <-> JS communication related methods.
+ addWebUIListener: addWebUIListener,
+ removeWebUIListener: removeWebUIListener,
+ sendWithPromise: sendWithPromise,
+ webUIListenerCallback: webUIListenerCallback,
+ webUIResponse: webUIResponse,
+
+ get doc() {
+ return document;
+ },
+
+ /** Whether we are using a Mac or not. */
+ get isMac() {
+ return /Mac/.test(navigator.platform);
+ },
+
+ /** Whether this is on the Windows platform or not. */
+ get isWindows() {
+ return /Win/.test(navigator.platform);
+ },
+
+ /** Whether this is on chromeOS or not. */
+ get isChromeOS() {
+ return /CrOS/.test(navigator.userAgent);
+ },
+
+ /** Whether this is on vanilla Linux (not chromeOS). */
+ get isLinux() {
+ return /Linux/.test(navigator.userAgent);
+ },
+
+ /** Whether this is on Android. */
+ get isAndroid() {
+ return /Android/.test(navigator.userAgent);
+ },
+
+ /** Whether this is on iOS. */
+ get isIOS() {
+ return /iPad|iPhone|iPod/.test(navigator.platform);
+ }
+ };
+}();
diff --git a/chrome/event_target.js b/chrome/event_target.js
new file mode 100644
index 0000000..24cdf29
--- /dev/null
+++ b/chrome/event_target.js
@@ -0,0 +1,102 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview This contains an implementation of the EventTarget interface
+ * as defined by DOM Level 2 Events.
+ */
+
+/**
+ * @typedef {EventListener|function(!Event):*}
+ */
+var EventListenerType;
+
+cr.define('cr', function() {
+
+ /**
+ * Creates a new EventTarget. This class implements the DOM level 2
+ * EventTarget interface and can be used wherever those are used.
+ * @constructor
+ * @implements {EventTarget}
+ */
+ function EventTarget() {}
+
+ EventTarget.prototype = {
+ /**
+ * Adds an event listener to the target.
+ * @param {string} type The name of the event.
+ * @param {EventListenerType} handler The handler for the event. This is
+ * called when the event is dispatched.
+ */
+ addEventListener: function(type, handler) {
+ if (!this.listeners_)
+ this.listeners_ = Object.create(null);
+ if (!(type in this.listeners_)) {
+ this.listeners_[type] = [handler];
+ } else {
+ var handlers = this.listeners_[type];
+ if (handlers.indexOf(handler) < 0)
+ handlers.push(handler);
+ }
+ },
+
+ /**
+ * Removes an event listener from the target.
+ * @param {string} type The name of the event.
+ * @param {EventListenerType} handler The handler for the event.
+ */
+ removeEventListener: function(type, handler) {
+ if (!this.listeners_)
+ return;
+ if (type in this.listeners_) {
+ var handlers = this.listeners_[type];
+ var index = handlers.indexOf(handler);
+ if (index >= 0) {
+ // Clean up if this was the last listener.
+ if (handlers.length == 1)
+ delete this.listeners_[type];
+ else
+ handlers.splice(index, 1);
+ }
+ }
+ },
+
+ /**
+ * Dispatches an event and calls all the listeners that are listening to
+ * the type of the event.
+ * @param {!Event} event The event to dispatch.
+ * @return {boolean} Whether the default action was prevented. If someone
+ * calls preventDefault on the event object then this returns false.
+ */
+ dispatchEvent: function(event) {
+ if (!this.listeners_)
+ return true;
+
+ // Since we are using DOM Event objects we need to override some of the
+ // properties and methods so that we can emulate this correctly.
+ var self = this;
+ event.__defineGetter__('target', function() {
+ return self;
+ });
+
+ var type = event.type;
+ var prevented = 0;
+ if (type in this.listeners_) {
+ // Clone to prevent removal during dispatch
+ var handlers = this.listeners_[type].concat();
+ for (var i = 0, handler; handler = handlers[i]; i++) {
+ if (handler.handleEvent)
+ prevented |= handler.handleEvent.call(handler, event) === false;
+ else
+ prevented |= handler.call(this, event) === false;
+ }
+ }
+
+ return !prevented && !event.defaultPrevented;
+ }
+ };
+
+ // Export
+ return {EventTarget: EventTarget};
+});
diff --git a/chrome/focus_outline_manager.js b/chrome/focus_outline_manager.js
new file mode 100644
index 0000000..23ef152
--- /dev/null
+++ b/chrome/focus_outline_manager.js
@@ -0,0 +1,106 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('cr.ui', function() {
+ /**
+ * The class name to set on the document element.
+ * @const
+ */
+ var CLASS_NAME = 'focus-outline-visible';
+
+ /**
+ * This class sets a CSS class name on the HTML element of |doc| when the user
+ * presses the tab key. It removes the class name when the user clicks
+ * anywhere.
+ *
+ * This allows you to write CSS like this:
+ *
+ * html.focus-outline-visible my-element:focus {
+ * outline: 5px auto -webkit-focus-ring-color;
+ * }
+ *
+ * And the outline will only be shown if the user uses the keyboard to get to
+ * it.
+ *
+ * @param {Document} doc The document to attach the focus outline manager to.
+ * @constructor
+ */
+ function FocusOutlineManager(doc) {
+ this.classList_ = doc.documentElement.classList;
+
+ var self = this;
+
+ doc.addEventListener('keydown', function(e) {
+ self.focusByKeyboard_ = true;
+ }, true);
+
+ doc.addEventListener('mousedown', function(e) {
+ self.focusByKeyboard_ = false;
+ }, true);
+
+ doc.addEventListener('focus', function(event) {
+ // Update visibility only when focus is actually changed.
+ self.updateVisibility();
+ }, true);
+
+ doc.addEventListener('focusout', function(event) {
+ window.setTimeout(function() {
+ if (!doc.hasFocus()) {
+ self.focusByKeyboard_ = true;
+ self.updateVisibility();
+ }
+ }, 0);
+ });
+
+ this.updateVisibility();
+ }
+
+ FocusOutlineManager.prototype = {
+ /**
+ * Whether focus change is triggered by TAB key.
+ * @type {boolean}
+ * @private
+ */
+ focusByKeyboard_: true,
+
+ updateVisibility: function() {
+ this.visible = this.focusByKeyboard_;
+ },
+
+ /**
+ * Whether the focus outline should be visible.
+ * @type {boolean}
+ */
+ set visible(visible) {
+ this.classList_.toggle(CLASS_NAME, visible);
+ },
+ get visible() {
+ return this.classList_.contains(CLASS_NAME);
+ }
+ };
+
+ /**
+ * Array of Document and FocusOutlineManager pairs.
+ * @type {Array}
+ */
+ var docsToManager = [];
+
+ /**
+ * Gets a per document singleton focus outline manager.
+ * @param {Document} doc The document to get the |FocusOutlineManager| for.
+ * @return {cr.ui.FocusOutlineManager} The per document singleton focus
+ * outline manager.
+ */
+ FocusOutlineManager.forDocument = function(doc) {
+ for (var i = 0; i < docsToManager.length; i++) {
+ if (doc == docsToManager[i][0])
+ return docsToManager[i][1];
+ }
+ var manager = new FocusOutlineManager(doc);
+ docsToManager.push([doc, manager]);
+ return manager;
+ };
+
+ return {FocusOutlineManager: FocusOutlineManager};
+});
diff --git a/chrome/gpu_internals.js b/chrome/gpu_internals.js
new file mode 100644
index 0000000..c48737b
--- /dev/null
+++ b/chrome/gpu_internals.js
@@ -0,0 +1,527 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// // Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+cr.define('gpu', function() {
+ /**
+ * This class provides a 'bridge' for communicating between javascript and the
+ * browser. When run outside of WebUI, e.g. as a regular webpage, it provides
+ * synthetic data to assist in testing.
+ * @constructor
+ */
+ function BrowserBridge() {
+ // If we are not running inside WebUI, output chrome.send messages
+ // to the console to help with quick-iteration debugging.
+ this.debugMode_ = (chrome.send === undefined && console.log);
+ if (this.debugMode_) {
+ var browserBridgeTests = document.createElement('script');
+ browserBridgeTests.src = 'browser_bridge_tests.js';
+ document.body.appendChild(browserBridgeTests);
+ }
+
+ this.nextRequestId_ = 0;
+ this.pendingCallbacks_ = [];
+ this.logMessages_ = [];
+
+ // Tell c++ code that we are ready to receive GPU Info.
+ if (!this.debugMode_) {
+ chrome.send('browserBridgeInitialized');
+ this.beginRequestClientInfo_();
+ this.beginRequestLogMessages_();
+ }
+ }
+
+ BrowserBridge.prototype = {
+ __proto__: cr.EventTarget.prototype,
+
+ applySimulatedData_: function applySimulatedData(data) {
+ // set up things according to the simulated data
+ this.gpuInfo_ = data.gpuInfo;
+ this.clientInfo_ = data.clientInfo;
+ this.logMessages_ = data.logMessages;
+ cr.dispatchSimpleEvent(this, 'gpuInfoUpdate');
+ cr.dispatchSimpleEvent(this, 'clientInfoChange');
+ cr.dispatchSimpleEvent(this, 'logMessagesChange');
+ },
+
+ /**
+ * Returns true if the page is hosted inside Chrome WebUI
+ * Helps have behavior conditional to emulate_webui.py
+ */
+ get debugMode() {
+ return this.debugMode_;
+ },
+
+ /**
+ * Sends a message to the browser with specified args. The
+ * browser will reply asynchronously via the provided callback.
+ */
+ callAsync: function(submessage, args, callback) {
+ var requestId = this.nextRequestId_;
+ this.nextRequestId_ += 1;
+ this.pendingCallbacks_[requestId] = callback;
+ if (!args) {
+ chrome.send('callAsync', [requestId.toString(), submessage]);
+ } else {
+ var allArgs = [requestId.toString(), submessage].concat(args);
+ chrome.send('callAsync', allArgs);
+ }
+ },
+
+ /**
+ * Called by gpu c++ code when client info is ready.
+ */
+ onCallAsyncReply: function(requestId, args) {
+ if (this.pendingCallbacks_[requestId] === undefined) {
+ throw new Error('requestId ' + requestId + ' is not pending');
+ }
+ var callback = this.pendingCallbacks_[requestId];
+ callback(args);
+ delete this.pendingCallbacks_[requestId];
+ },
+
+ /**
+ * Get gpuInfo data.
+ */
+ get gpuInfo() {
+ return this.gpuInfo_;
+ },
+
+ /**
+ * Called from gpu c++ code when GPU Info is updated.
+ */
+ onGpuInfoUpdate: function(gpuInfo) {
+ this.gpuInfo_ = gpuInfo;
+ cr.dispatchSimpleEvent(this, 'gpuInfoUpdate');
+ },
+
+ /**
+ * This function begins a request for the ClientInfo. If it comes back
+ * as undefined, then we will issue the request again in 250ms.
+ */
+ beginRequestClientInfo_: function() {
+ this.callAsync('requestClientInfo', undefined, (function(data) {
+ if (data === undefined) { // try again in 250 ms
+ window.setTimeout(this.beginRequestClientInfo_.bind(this), 250);
+ } else {
+ this.clientInfo_ = data;
+ cr.dispatchSimpleEvent(this, 'clientInfoChange');
+ }
+ }).bind(this));
+ },
+
+ /**
+ * Returns information about the currently running Chrome build.
+ */
+ get clientInfo() {
+ return this.clientInfo_;
+ },
+
+ /**
+ * This function checks for new GPU_LOG messages.
+ * If any are found, a refresh is triggered.
+ */
+ beginRequestLogMessages_: function() {
+ this.callAsync('requestLogMessages', undefined,
+ (function(messages) {
+ if (messages.length != this.logMessages_.length) {
+ this.logMessages_ = messages;
+ cr.dispatchSimpleEvent(this, 'logMessagesChange');
+ }
+ // check again in 250 ms
+ window.setTimeout(this.beginRequestLogMessages_.bind(this), 250);
+ }).bind(this));
+ },
+
+ /**
+ * Returns an array of log messages issued by the GPU process, if any.
+ */
+ get logMessages() {
+ return this.logMessages_;
+ },
+
+ /**
+ * Returns the value of the "Sandboxed" row.
+ */
+ isSandboxedForTesting : function() {
+ for (i = 0; i < this.gpuInfo_.basic_info.length; ++i) {
+ var info = this.gpuInfo_.basic_info[i];
+ if (info.description == "Sandboxed")
+ return info.value;
+ }
+ return false;
+ }
+ };
+
+ return {
+ BrowserBridge: BrowserBridge
+ };
+});
+
+// // Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+
+/**
+ * @fileoverview This view displays information on the current GPU
+ * hardware. Its primary usefulness is to allow users to copy-paste
+ * their data in an easy to read format for bug reports.
+ */
+cr.define('gpu', function() {
+ /**
+ * Provides information on the GPU process and underlying graphics hardware.
+ * @constructor
+ * @extends {cr.ui.TabPanel}
+ */
+ var InfoView = cr.ui.define(cr.ui.TabPanel);
+
+ InfoView.prototype = {
+ __proto__: cr.ui.TabPanel.prototype,
+
+ decorate: function() {
+ cr.ui.TabPanel.prototype.decorate.apply(this);
+
+ browserBridge.addEventListener('gpuInfoUpdate', this.refresh.bind(this));
+ browserBridge.addEventListener('logMessagesChange',
+ this.refresh.bind(this));
+ browserBridge.addEventListener('clientInfoChange',
+ this.refresh.bind(this));
+ this.refresh();
+ },
+
+ /**
+ * Updates the view based on its currently known data
+ */
+ refresh: function(data) {
+ // Client info
+ if (browserBridge.clientInfo) {
+ var clientInfo = browserBridge.clientInfo;
+
+ this.setTable_('client-info', [
+ {
+ description: 'Data exported',
+ value: (new Date()).toLocaleString()
+ },
+ {
+ description: 'Chrome version',
+ value: clientInfo.version
+ },
+ {
+ description: 'Operating system',
+ value: clientInfo.operating_system
+ },
+ {
+ description: 'Software rendering list version',
+ value: clientInfo.blacklist_version
+ },
+ {
+ description: 'Driver bug list version',
+ value: clientInfo.driver_bug_list_version
+ },
+ {
+ description: 'ANGLE commit id',
+ value: clientInfo.angle_commit_id
+ },
+ {
+ description: '2D graphics backend',
+ value: clientInfo.graphics_backend
+ },
+ {
+ description: 'Command Line',
+ value: clientInfo.command_line
+ }]);
+ } else {
+ this.setText_('client-info', '... loading...');
+ }
+
+ // Feature map
+ var featureLabelMap = {
+ '2d_canvas': 'Canvas',
+ 'gpu_compositing': 'Compositing',
+ 'webgl': 'WebGL',
+ 'multisampling': 'WebGL multisampling',
+ 'flash_3d': 'Flash',
+ 'flash_stage3d': 'Flash Stage3D',
+ 'flash_stage3d_baseline': 'Flash Stage3D Baseline profile',
+ 'texture_sharing': 'Texture Sharing',
+ 'video_decode': 'Video Decode',
+ 'video_encode': 'Video Encode',
+ 'panel_fitting': 'Panel Fitting',
+ 'rasterization': 'Rasterization',
+ 'multiple_raster_threads': 'Multiple Raster Threads',
+ 'native_gpu_memory_buffers': 'Native GpuMemoryBuffers',
+ 'vpx_decode': 'VPx Video Decode',
+ 'webgl2': 'WebGL2',
+ 'checker_imaging': 'CheckerImaging',
+ };
+
+ var statusMap = {
+ 'disabled_software': {
+ 'label': 'Software only. Hardware acceleration disabled',
+ 'class': 'feature-yellow'
+ },
+ 'disabled_off': {
+ 'label': 'Disabled',
+ 'class': 'feature-red'
+ },
+ 'disabled_off_ok': {
+ 'label': 'Disabled',
+ 'class': 'feature-yellow'
+ },
+ 'unavailable_software': {
+ 'label': 'Software only, hardware acceleration unavailable',
+ 'class': 'feature-yellow'
+ },
+ 'unavailable_off': {
+ 'label': 'Unavailable',
+ 'class': 'feature-red'
+ },
+ 'unavailable_off_ok': {
+ 'label': 'Unavailable',
+ 'class': 'feature-yellow'
+ },
+ 'enabled_readback': {
+ 'label': 'Hardware accelerated but at reduced performance',
+ 'class': 'feature-yellow'
+ },
+ 'enabled_force': {
+ 'label': 'Hardware accelerated on all pages',
+ 'class': 'feature-green'
+ },
+ 'enabled': {
+ 'label': 'Hardware accelerated',
+ 'class': 'feature-green'
+ },
+ 'enabled_on': {
+ 'label': 'Enabled',
+ 'class': 'feature-green'
+ },
+ 'enabled_force_on': {
+ 'label': 'Force enabled',
+ 'class': 'feature-green'
+ },
+ };
+
+ // GPU info, basic
+ var diagnosticsDiv = this.querySelector('.diagnostics');
+ var diagnosticsLoadingDiv = this.querySelector('.diagnostics-loading');
+ var featureStatusList = this.querySelector('.feature-status-list');
+ var problemsDiv = this.querySelector('.problems-div');
+ var problemsList = this.querySelector('.problems-list');
+ var workaroundsDiv = this.querySelector('.workarounds-div');
+ var workaroundsList = this.querySelector('.workarounds-list');
+ var gpuInfo = browserBridge.gpuInfo;
+ var i;
+ if (gpuInfo) {
+ // Not using jstemplate here for blacklist status because we construct
+ // href from data, which jstemplate can't seem to do.
+ if (gpuInfo.featureStatus) {
+ // feature status list
+ featureStatusList.textContent = '';
+ for (var featureName in gpuInfo.featureStatus.featureStatus) {
+ var featureStatus =
+ gpuInfo.featureStatus.featureStatus[featureName];
+ var featureEl = document.createElement('li');
+
+ var nameEl = document.createElement('span');
+ if (!featureLabelMap[featureName])
+ console.log('Missing featureLabel for', featureName);
+ nameEl.textContent = featureLabelMap[featureName] + ': ';
+ featureEl.appendChild(nameEl);
+
+ var statusEl = document.createElement('span');
+ var statusInfo = statusMap[featureStatus];
+ if (!statusInfo) {
+ console.log('Missing status for ', featureStatus);
+ statusEl.textContent = 'Unknown';
+ statusEl.className = 'feature-red';
+ } else {
+ statusEl.textContent = statusInfo['label'];
+ statusEl.className = statusInfo['class'];
+ }
+ featureEl.appendChild(statusEl);
+
+ featureStatusList.appendChild(featureEl);
+ }
+
+ // problems list
+ if (gpuInfo.featureStatus.problems.length) {
+ problemsDiv.hidden = false;
+ problemsList.textContent = '';
+ for (i = 0; i < gpuInfo.featureStatus.problems.length; i++) {
+ var problem = gpuInfo.featureStatus.problems[i];
+ var problemEl = this.createProblemEl_(problem);
+ problemsList.appendChild(problemEl);
+ }
+ } else {
+ problemsDiv.hidden = true;
+ }
+
+ // driver bug workarounds list
+ if (gpuInfo.featureStatus.workarounds.length) {
+ workaroundsDiv.hidden = false;
+ workaroundsList.textContent = '';
+ for (i = 0; i < gpuInfo.featureStatus.workarounds.length; i++) {
+ var workaroundEl = document.createElement('li');
+ workaroundEl.textContent = gpuInfo.featureStatus.workarounds[i];
+ workaroundsList.appendChild(workaroundEl);
+ }
+ } else {
+ workaroundsDiv.hidden = true;
+ }
+
+ } else {
+ featureStatusList.textContent = '';
+ problemsList.hidden = true;
+ workaroundsList.hidden = true;
+ }
+
+ if (gpuInfo.basic_info)
+ this.setTable_('basic-info', gpuInfo.basic_info);
+ else
+ this.setTable_('basic-info', []);
+
+ if (gpuInfo.compositorInfo)
+ this.setTable_('compositor-info', gpuInfo.compositorInfo);
+ else
+ this.setTable_('compositor-info', []);
+
+ if (gpuInfo.gpuMemoryBufferInfo)
+ this.setTable_('gpu-memory-buffer-info', gpuInfo.gpuMemoryBufferInfo);
+ else
+ this.setTable_('gpu-memory-buffer-info', []);
+
+ if (gpuInfo.diagnostics) {
+ diagnosticsDiv.hidden = false;
+ diagnosticsLoadingDiv.hidden = true;
+ $('diagnostics-table').hidden = false;
+ this.setTable_('diagnostics-table', gpuInfo.diagnostics);
+ } else if (gpuInfo.diagnostics === null) {
+ // gpu_internals.cc sets diagnostics to null when it is being loaded
+ diagnosticsDiv.hidden = false;
+ diagnosticsLoadingDiv.hidden = false;
+ $('diagnostics-table').hidden = true;
+ } else {
+ diagnosticsDiv.hidden = true;
+ }
+ } else {
+ this.setText_('basic-info', '... loading ...');
+ diagnosticsDiv.hidden = true;
+ featureStatusList.textContent = '';
+ problemsDiv.hidden = true;
+ }
+
+ // Log messages
+ jstProcess(new JsEvalContext({values: browserBridge.logMessages}),
+ $('log-messages'));
+ },
+
+ createProblemEl_: function(problem) {
+ var problemEl;
+ problemEl = document.createElement('li');
+
+ // Description of issue
+ var desc = document.createElement('a');
+ desc.textContent = problem.description;
+ problemEl.appendChild(desc);
+
+ // Spacing ':' element
+ if (problem.crBugs.length > 0) {
+ var tmp = document.createElement('span');
+ tmp.textContent = ': ';
+ problemEl.appendChild(tmp);
+ }
+
+ var nbugs = 0;
+ var j;
+
+ // crBugs
+ for (j = 0; j < problem.crBugs.length; ++j) {
+ if (nbugs > 0) {
+ var tmp = document.createElement('span');
+ tmp.textContent = ', ';
+ problemEl.appendChild(tmp);
+ }
+
+ var link = document.createElement('a');
+ var bugid = parseInt(problem.crBugs[j]);
+ link.textContent = bugid;
+ link.href = 'http://crbug.com/' + bugid;
+ problemEl.appendChild(link);
+ nbugs++;
+ }
+
+ if (problem.affectedGpuSettings.length > 0) {
+ var brNode = document.createElement('br');
+ problemEl.appendChild(brNode);
+
+ var iNode = document.createElement('i');
+ problemEl.appendChild(iNode);
+
+ var headNode = document.createElement('span');
+ if (problem.tag == 'disabledFeatures')
+ headNode.textContent = 'Disabled Features: ';
+ else // problem.tag == 'workarounds'
+ headNode.textContent = 'Applied Workarounds: ';
+ iNode.appendChild(headNode);
+ for (j = 0; j < problem.affectedGpuSettings.length; ++j) {
+ if (j > 0) {
+ var separateNode = document.createElement('span');
+ separateNode.textContent = ', ';
+ iNode.appendChild(separateNode);
+ }
+ var nameNode = document.createElement('span');
+ if (problem.tag == 'disabledFeatures')
+ nameNode.classList.add('feature-red');
+ else // problem.tag == 'workarounds'
+ nameNode.classList.add('feature-yellow');
+ nameNode.textContent = problem.affectedGpuSettings[j];
+ iNode.appendChild(nameNode);
+ }
+ }
+
+ return problemEl;
+ },
+
+ setText_: function(outputElementId, text) {
+ var peg = document.getElementById(outputElementId);
+ peg.textContent = text;
+ },
+
+ setTable_: function(outputElementId, inputData) {
+ var template = jstGetTemplate('info-view-table-template');
+ jstProcess(new JsEvalContext({value: inputData}),
+ template);
+
+ var peg = document.getElementById(outputElementId);
+ if (!peg)
+ throw new Error('Node ' + outputElementId + ' not found');
+
+ peg.innerHTML = '';
+ peg.appendChild(template);
+ }
+ };
+
+ return {
+ InfoView: InfoView
+ };
+});
+
+
+var browserBridge;
+
+/**
+ * Main entry point. called once the page has loaded.
+ */
+function onLoad() {
+ browserBridge = new gpu.BrowserBridge();
+
+ // Create the views.
+ cr.ui.decorate('#info-view', gpu.InfoView);
+}
+
+document.addEventListener('DOMContentLoaded', onLoad);
diff --git a/chrome/index.html b/chrome/index.html
new file mode 100644
index 0000000..5c7781b
--- /dev/null
+++ b/chrome/index.html
@@ -0,0 +1,54 @@
+
+
+
+check.wit.com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
All objects in cr
+
+
+
+
+
+
All objects in browserBridge
+
+
+
diff --git a/chrome/load_time_data.js b/chrome/load_time_data.js
new file mode 100644
index 0000000..3439a24
--- /dev/null
+++ b/chrome/load_time_data.js
@@ -0,0 +1,212 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview This file defines a singleton which provides access to all data
+ * that is available as soon as the page's resources are loaded (before DOM
+ * content has finished loading). This data includes both localized strings and
+ * any data that is important to have ready from a very early stage (e.g. things
+ * that must be displayed right away).
+ *
+ * Note that loadTimeData is not guaranteed to be consistent between page
+ * refreshes (https://crbug.com/740629) and should not contain values that might
+ * change if the page is re-opened later.
+ */
+
+/** @type {!LoadTimeData} */ var loadTimeData;
+
+// Expose this type globally as a temporary work around until
+// https://github.com/google/closure-compiler/issues/544 is fixed.
+/** @constructor */
+function LoadTimeData(){}
+
+(function() {
+ 'use strict';
+
+ LoadTimeData.prototype = {
+ /**
+ * Sets the backing object.
+ *
+ * Note that there is no getter for |data_| to discourage abuse of the form:
+ *
+ * var value = loadTimeData.data()['key'];
+ *
+ * @param {Object} value The de-serialized page data.
+ */
+ set data(value) {
+ expect(!this.data_, 'Re-setting data.');
+ this.data_ = value;
+ },
+
+ /**
+ * Returns a JsEvalContext for |data_|.
+ * @returns {JsEvalContext}
+ */
+ createJsEvalContext: function() {
+ return new JsEvalContext(this.data_);
+ },
+
+ /**
+ * @param {string} id An ID of a value that might exist.
+ * @return {boolean} True if |id| is a key in the dictionary.
+ */
+ valueExists: function(id) {
+ return id in this.data_;
+ },
+
+ /**
+ * Fetches a value, expecting that it exists.
+ * @param {string} id The key that identifies the desired value.
+ * @return {*} The corresponding value.
+ */
+ getValue: function(id) {
+ expect(this.data_, 'No data. Did you remember to include strings.js?');
+ var value = this.data_[id];
+ expect(typeof value != 'undefined', 'Could not find value for ' + id);
+ return value;
+ },
+
+ /**
+ * As above, but also makes sure that the value is a string.
+ * @param {string} id The key that identifies the desired string.
+ * @return {string} The corresponding string value.
+ */
+ getString: function(id) {
+ var value = this.getValue(id);
+ expectIsType(id, value, 'string');
+ return /** @type {string} */ (value);
+ },
+
+ /**
+ * Returns a formatted localized string where $1 to $9 are replaced by the
+ * second to the tenth argument.
+ * @param {string} id The ID of the string we want.
+ * @param {...(string|number)} var_args The extra values to include in the
+ * formatted output.
+ * @return {string} The formatted string.
+ */
+ getStringF: function(id, var_args) {
+ var value = this.getString(id);
+ if (!value)
+ return '';
+
+ var args = Array.prototype.slice.call(arguments);
+ args[0] = value;
+ return this.substituteString.apply(this, args);
+ },
+
+ /**
+ * Returns a formatted localized string where $1 to $9 are replaced by the
+ * second to the tenth argument. Any standalone $ signs must be escaped as
+ * $$.
+ * @param {string} label The label to substitute through.
+ * This is not an resource ID.
+ * @param {...(string|number)} var_args The extra values to include in the
+ * formatted output.
+ * @return {string} The formatted string.
+ */
+ substituteString: function(label, var_args) {
+ var varArgs = arguments;
+ return label.replace(/\$(.|$|\n)/g, function(m) {
+ assert(m.match(/\$[$1-9]/), 'Unescaped $ found in localized string.');
+ return m == '$$' ? '$' : varArgs[m[1]];
+ });
+ },
+
+ /**
+ * Returns a formatted string where $1 to $9 are replaced by the second to
+ * tenth argument, split apart into a list of pieces describing how the
+ * substitution was performed. Any standalone $ signs must be escaped as $$.
+ * @param {string} label A localized string to substitute through.
+ * This is not an resource ID.
+ * @param {...(string|number)} var_args The extra values to include in the
+ * formatted output.
+ * @return {!Array} The formatted
+ * string pieces.
+ */
+ getSubstitutedStringPieces: function(label, var_args) {
+ var varArgs = arguments;
+ // Split the string by separately matching all occurrences of $1-9 and of
+ // non $1-9 pieces.
+ var pieces = (label.match(/(\$[1-9])|(([^$]|\$([^1-9]|$))+)/g) ||
+ []).map(function(p) {
+ // Pieces that are not $1-9 should be returned after replacing $$
+ // with $.
+ if (!p.match(/^\$[1-9]$/)) {
+ assert(
+ (p.match(/\$/g) || []).length % 2 == 0,
+ 'Unescaped $ found in localized string.');
+ return {value: p.replace(/\$\$/g, '$'), arg: null};
+ }
+
+ // Otherwise, return the substitution value.
+ return {value: varArgs[p[1]], arg: p};
+ });
+
+ return pieces;
+ },
+
+ /**
+ * As above, but also makes sure that the value is a boolean.
+ * @param {string} id The key that identifies the desired boolean.
+ * @return {boolean} The corresponding boolean value.
+ */
+ getBoolean: function(id) {
+ var value = this.getValue(id);
+ expectIsType(id, value, 'boolean');
+ return /** @type {boolean} */ (value);
+ },
+
+ /**
+ * As above, but also makes sure that the value is an integer.
+ * @param {string} id The key that identifies the desired number.
+ * @return {number} The corresponding number value.
+ */
+ getInteger: function(id) {
+ var value = this.getValue(id);
+ expectIsType(id, value, 'number');
+ expect(value == Math.floor(value), 'Number isn\'t integer: ' + value);
+ return /** @type {number} */ (value);
+ },
+
+ /**
+ * Override values in loadTimeData with the values found in |replacements|.
+ * @param {Object} replacements The dictionary object of keys to replace.
+ */
+ overrideValues: function(replacements) {
+ expect(
+ typeof replacements == 'object',
+ 'Replacements must be a dictionary object.');
+ for (var key in replacements) {
+ this.data_[key] = replacements[key];
+ }
+ }
+ };
+
+ /**
+ * Checks condition, displays error message if expectation fails.
+ * @param {*} condition The condition to check for truthiness.
+ * @param {string} message The message to display if the check fails.
+ */
+ function expect(condition, message) {
+ if (!condition) {
+ console.error(
+ 'Unexpected condition on ' + document.location.href + ': ' + message);
+ }
+ }
+
+ /**
+ * Checks that the given value has the given type.
+ * @param {string} id The id of the value (only used for error message).
+ * @param {*} value The value to check the type on.
+ * @param {string} type The type we expect |value| to be.
+ */
+ function expectIsType(id, value, type) {
+ expect(
+ typeof value == type, '[' + value + '] (' + id + ') is not a ' + type);
+ }
+
+ expect(!loadTimeData, 'should only include this file once');
+ loadTimeData = new LoadTimeData;
+})();
diff --git a/chrome/strings.js b/chrome/strings.js
new file mode 100644
index 0000000..f981b4b
--- /dev/null
+++ b/chrome/strings.js
@@ -0,0 +1 @@
+loadTimeData.data = {"fontfamily":"Ubuntu, Arial, sans-serif","fontsize":"75%","language":"en","textdirection":"ltr"};
diff --git a/chrome/tabs.css b/chrome/tabs.css
new file mode 100644
index 0000000..018c6f1
--- /dev/null
+++ b/chrome/tabs.css
@@ -0,0 +1,119 @@
+/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+tabbox {
+ -webkit-box-orient: vertical;
+ display: -webkit-box;
+}
+
+tabs {
+ -webkit-padding-start: 8px;
+ background: -webkit-linear-gradient(white, rgb(243, 243, 243));
+ border-bottom: 1px solid rgb(160, 160, 160);
+ display: -webkit-box;
+ margin: 0;
+}
+
+/* New users of tabs.css should add 'new-style-tabs' to the class list of any
+ * 'tabs' or 'tabpanels' elements.
+ *
+ * TODO(rfevang): Remove when all users are converted to the new style.
+ * (crbug.com/247772). */
+tabs.new-style-tabs {
+ -webkit-padding-start: 9px;
+ background: #fbfbfb;
+ border-bottom: 1px solid #c8c8c8;
+ padding-top: 14px;
+}
+
+tabs > * {
+ -webkit-margin-start: 5px;
+ background: rgba(160, 160, 160, .3);
+ border: 1px solid rgba(160, 160, 160, .3);
+ border-bottom: 0;
+ border-top-left-radius: 3px;
+ border-top-right-radius: 3px;
+ cursor: default;
+ display: block;
+ min-width: 4em;
+ padding: 2px 10px;
+ text-align: center;
+ transition: border-color 150ms, background-color 150ms;
+}
+
+tabs.new-style-tabs > * {
+ -webkit-margin-start: 0;
+ background: #fbfbfb;
+ border: 1px solid #fbfbfb;
+ border-bottom: 0;
+ border-radius: 0;
+ min-width: 0;
+ padding: 4px 9px 4px 10px;
+ transition: none;
+}
+
+tabs > :not([selected]) {
+ background: rgba(238, 238, 238, .3);
+}
+
+tabs.new-style-tabs > :not([selected]) {
+ background: #fbfbfb;
+ color: #646464;
+}
+
+tabs > :not([selected]):hover {
+ background: rgba(247, 247, 247, .3);
+}
+
+tabs.new-style-tabs > :not([selected]):hover {
+ background: #fbfbfb;
+ color: black;
+}
+
+tabs > [selected] {
+ background: white;
+ border-color: rgb(160, 160, 160);
+ margin-bottom: -1px;
+ position: relative;
+ transition: none;
+ z-index: 0;
+}
+
+tabs.new-style-tabs > [selected] {
+ background: #fbfbfb;
+ border-color: #c8c8c8;
+ font-weight: bold;
+}
+
+tabs:focus {
+ outline: none;
+}
+
+html.focus-outline-visible tabs:focus > [selected] {
+ outline: 5px auto -webkit-focus-ring-color;
+ outline-offset: -2px;
+}
+
+tabpanels {
+ -webkit-box-flex: 1;
+ background: white;
+ box-shadow: 2px 2px 5px rgba(0, 0, 0, .2);
+ display: -webkit-box;
+ padding: 5px 15px 0 15px;
+}
+
+tabpanels.new-style-tabs {
+ background: #fbfbfb;
+ box-shadow: none;
+ padding: 0 20px;
+}
+
+tabpanels > * {
+ -webkit-box-flex: 1;
+ display: none;
+}
+
+tabpanels > [selected] {
+ display: block;
+}
diff --git a/chrome/tabs.js b/chrome/tabs.js
new file mode 100644
index 0000000..deccce9
--- /dev/null
+++ b/chrome/tabs.js
@@ -0,0 +1,241 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('cr.ui', function() {
+
+ /**
+ * Returns the TabBox for a Tab or a TabPanel.
+ * @param {Tab|TabPanel} el The tab or tabpanel element.
+ * @return {TabBox} The tab box if found.
+ */
+ function getTabBox(el) {
+ return findAncestor(el, function(node) {
+ return node.tagName == 'TABBOX';
+ });
+ }
+
+ /**
+ * Returns whether an element is a tab related object.
+ * @param {HTMLElement} el The element whose tag is being checked
+ * @return {boolean} Whether the element is a tab related element.
+ */
+ function isTabElement(el) {
+ return el.tagName == 'TAB' || el.tagName == 'TABPANEL';
+ }
+
+ /**
+ * Set hook for the selected property for Tab and TabPanel.
+ * This sets the selectedIndex on the parent TabBox.
+ * @param {boolean} newValue The new selected value
+ * @param {boolean} oldValue The old selected value. (This is ignored atm.)
+ * @this {Tab|TabPanel}
+ */
+ function selectedSetHook(newValue, oldValue) {
+ var tabBox;
+ if (newValue && (tabBox = getTabBox(this)))
+ tabBox.selectedIndex = Array.prototype.indexOf.call(p.children, this);
+ }
+
+ /**
+ * Decorates all the children of an element.
+ * @this {HTMLElement}
+ */
+ function decorateChildren() {
+ var map = {
+ TABBOX: TabBox,
+ TABS: Tabs,
+ TAB: Tab,
+ TABPANELS: TabPanels,
+ TABPANEL: TabPanel
+ };
+
+ Object.keys(map).forEach(function(tagName) {
+ var children = this.getElementsByTagName(tagName);
+ var constr = map[tagName];
+ for (var i = 0; child = children[i]; i++) {
+ cr.ui.decorate(child, constr);
+ }
+ }.bind(this));
+ }
+
+ /**
+ * Set hook for TabBox selectedIndex.
+ * @param {number} selectedIndex The new selected index.
+ * @this {TabBox}
+ */
+ function selectedIndexSetHook(selectedIndex) {
+ var child, tabChild, element;
+ element = this.querySelector('tabs');
+ if (element) {
+ for (var i = 0; child = element.children[i]; i++) {
+ child.selected = i == selectedIndex;
+ }
+ }
+
+ element = this.querySelector('tabpanels');
+ if (element) {
+ for (var i = 0; child = element.children[i]; i++) {
+ child.selected = i == selectedIndex;
+ }
+ }
+ }
+
+ /**
+ * Creates a new tabbox element.
+ * @param {Object=} opt_propertyBag Optional properties.
+ * @constructor
+ * @extends {HTMLElement}
+ */
+ var TabBox = cr.ui.define('tabbox');
+
+ TabBox.prototype = {
+ __proto__: HTMLElement.prototype,
+ decorate: function() {
+ decorateChildren.call(this);
+ this.addEventListener('selectedChange', this.handleSelectedChange_, true);
+ this.selectedIndex = 0;
+ },
+
+ /**
+ * Callback for when a Tab or TabPanel changes its selected property.
+ * @param {Event} e The property change event.
+ * @private
+ */
+ handleSelectedChange_: function(e) {
+ var target = e.target;
+ if (e.newValue && isTabElement(target) && getTabBox(target) == this) {
+ var index =
+ Array.prototype.indexOf.call(target.parentElement.children, target);
+ this.selectedIndex = index;
+ }
+ },
+
+ selectedIndex_: -1
+ };
+
+ /**
+ * The index of the selected tab or -1 if no tab is selected.
+ * @type {number}
+ */
+ cr.defineProperty(
+ TabBox, 'selectedIndex', cr.PropertyKind.JS_PROP, selectedIndexSetHook);
+
+ /**
+ * Creates a new tabs element.
+ * @param {string} opt_label The text label for the item.
+ * @constructor
+ * @extends {HTMLElement}
+ */
+ var Tabs = cr.ui.define('tabs');
+ Tabs.prototype = {
+ __proto__: HTMLElement.prototype,
+ decorate: function() {
+ decorateChildren.call(this);
+
+ // Make the Tabs element focusable.
+ this.tabIndex = 0;
+ this.addEventListener('keydown', this.handleKeyDown_.bind(this));
+
+ // Get (and initializes a focus outline manager.
+ this.focusOutlineManager_ =
+ cr.ui.FocusOutlineManager.forDocument(this.ownerDocument);
+ },
+
+ /**
+ * Handle keydown to change the selected tab when the user presses the
+ * arrow keys.
+ * @param {Event} e The keyboard event.
+ * @private
+ */
+ handleKeyDown_: function(e) {
+ var delta = 0;
+ switch (e.key) {
+ case 'ArrowLeft':
+ case 'ArrowUp':
+ delta = -1;
+ break;
+ case 'ArrowRight':
+ case 'ArrowDown':
+ delta = 1;
+ break;
+ }
+
+ if (!delta)
+ return;
+
+ var cs = this.ownerDocument.defaultView.getComputedStyle(this);
+ if (cs.direction == 'rtl')
+ delta *= -1;
+
+ var count = this.children.length;
+ var tabbox = getTabBox(this);
+ var index = tabbox.selectedIndex;
+ tabbox.selectedIndex = (index + delta + count) % count;
+
+ // Show focus outline since we used the keyboard.
+ this.focusOutlineManager_.visible = true;
+ }
+ };
+
+ /**
+ * Creates a new tab element.
+ * @param {string} opt_label The text label for the item.
+ * @constructor
+ * @extends {HTMLElement}
+ */
+ var Tab = cr.ui.define('tab');
+ Tab.prototype = {
+ __proto__: HTMLElement.prototype,
+ decorate: function() {
+ var self = this;
+ this.addEventListener(cr.isMac ? 'click' : 'mousedown', function() {
+ self.selected = true;
+ });
+ }
+ };
+
+ /**
+ * Whether the tab is selected.
+ * @type {boolean}
+ */
+ cr.defineProperty(Tab, 'selected', cr.PropertyKind.BOOL_ATTR);
+
+ /**
+ * Creates a new tabpanels element.
+ * @param {string} opt_label The text label for the item.
+ * @constructor
+ * @extends {HTMLElement}
+ */
+ var TabPanels = cr.ui.define('tabpanels');
+ TabPanels.prototype = {
+ __proto__: HTMLElement.prototype,
+ decorate: decorateChildren
+ };
+
+ /**
+ * Creates a new tabpanel element.
+ * @param {string} opt_label The text label for the item.
+ * @constructor
+ * @extends {HTMLElement}
+ */
+ var TabPanel = cr.ui.define('tabpanel');
+ TabPanel.prototype = {
+ __proto__: HTMLElement.prototype,
+ decorate: function() {}
+ };
+
+ /**
+ * Whether the tab is selected.
+ * @type {boolean}
+ */
+ cr.defineProperty(TabPanel, 'selected', cr.PropertyKind.BOOL_ATTR);
+
+ return {
+ TabBox: TabBox,
+ Tabs: Tabs,
+ Tab: Tab,
+ TabPanels: TabPanels,
+ TabPanel: TabPanel
+ };
+});
diff --git a/chrome/ui.js b/chrome/ui.js
new file mode 100644
index 0000000..9c9cc21
--- /dev/null
+++ b/chrome/ui.js
@@ -0,0 +1,212 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('cr.ui', function() {
+
+ /**
+ * Decorates elements as an instance of a class.
+ * @param {string|!Element} source The way to find the element(s) to decorate.
+ * If this is a string then {@code querySeletorAll} is used to find the
+ * elements to decorate.
+ * @param {!Function} constr The constructor to decorate with. The constr
+ * needs to have a {@code decorate} function.
+ */
+ function decorate(source, constr) {
+ var elements;
+ if (typeof source == 'string')
+ elements = cr.doc.querySelectorAll(source);
+ else
+ elements = [source];
+
+ for (var i = 0, el; el = elements[i]; i++) {
+ if (!(el instanceof constr))
+ constr.decorate(el);
+ }
+ }
+
+ /**
+ * Helper function for creating new element for define.
+ */
+ function createElementHelper(tagName, opt_bag) {
+ // Allow passing in ownerDocument to create in a different document.
+ var doc;
+ if (opt_bag && opt_bag.ownerDocument)
+ doc = opt_bag.ownerDocument;
+ else
+ doc = cr.doc;
+ return doc.createElement(tagName);
+ }
+
+ /**
+ * Creates the constructor for a UI element class.
+ *
+ * Usage:
+ *
+ *
+ * @param {string|Function} tagNameOrFunction The tagName or
+ * function to use for newly created elements. If this is a function it
+ * needs to return a new element when called.
+ * @return {function(Object=):Element} The constructor function which takes
+ * an optional property bag. The function also has a static
+ * {@code decorate} method added to it.
+ */
+ function define(tagNameOrFunction) {
+ var createFunction, tagName;
+ if (typeof tagNameOrFunction == 'function') {
+ createFunction = tagNameOrFunction;
+ tagName = '';
+ } else {
+ createFunction = createElementHelper;
+ tagName = tagNameOrFunction;
+ }
+
+ /**
+ * Creates a new UI element constructor.
+ * @param {Object=} opt_propertyBag Optional bag of properties to set on the
+ * object after created. The property {@code ownerDocument} is special
+ * cased and it allows you to create the element in a different
+ * document than the default.
+ * @constructor
+ */
+ function f(opt_propertyBag) {
+ var el = createFunction(tagName, opt_propertyBag);
+ f.decorate(el);
+ for (var propertyName in opt_propertyBag) {
+ el[propertyName] = opt_propertyBag[propertyName];
+ }
+ return el;
+ }
+
+ /**
+ * Decorates an element as a UI element class.
+ * @param {!Element} el The element to decorate.
+ */
+ f.decorate = function(el) {
+ el.__proto__ = f.prototype;
+ el.decorate();
+ };
+
+ return f;
+ }
+
+ /**
+ * Input elements do not grow and shrink with their content. This is a simple
+ * (and not very efficient) way of handling shrinking to content with support
+ * for min width and limited by the width of the parent element.
+ * @param {!HTMLElement} el The element to limit the width for.
+ * @param {!HTMLElement} parentEl The parent element that should limit the
+ * size.
+ * @param {number} min The minimum width.
+ * @param {number=} opt_scale Optional scale factor to apply to the width.
+ */
+ function limitInputWidth(el, parentEl, min, opt_scale) {
+ // Needs a size larger than borders
+ el.style.width = '10px';
+ var doc = el.ownerDocument;
+ var win = doc.defaultView;
+ var computedStyle = win.getComputedStyle(el);
+ var parentComputedStyle = win.getComputedStyle(parentEl);
+ var rtl = computedStyle.direction == 'rtl';
+
+ // To get the max width we get the width of the treeItem minus the position
+ // of the input.
+ var inputRect = el.getBoundingClientRect(); // box-sizing
+ var parentRect = parentEl.getBoundingClientRect();
+ var startPos = rtl ? parentRect.right - inputRect.right :
+ inputRect.left - parentRect.left;
+
+ // Add up border and padding of the input.
+ var inner = parseInt(computedStyle.borderLeftWidth, 10) +
+ parseInt(computedStyle.paddingLeft, 10) +
+ parseInt(computedStyle.paddingRight, 10) +
+ parseInt(computedStyle.borderRightWidth, 10);
+
+ // We also need to subtract the padding of parent to prevent it to overflow.
+ var parentPadding = rtl ? parseInt(parentComputedStyle.paddingLeft, 10) :
+ parseInt(parentComputedStyle.paddingRight, 10);
+
+ var max = parentEl.clientWidth - startPos - inner - parentPadding;
+ if (opt_scale)
+ max *= opt_scale;
+
+ function limit() {
+ if (el.scrollWidth > max) {
+ el.style.width = max + 'px';
+ } else {
+ el.style.width = 0;
+ var sw = el.scrollWidth;
+ if (sw < min) {
+ el.style.width = min + 'px';
+ } else {
+ el.style.width = sw + 'px';
+ }
+ }
+ }
+
+ el.addEventListener('input', limit);
+ limit();
+ }
+
+ /**
+ * Takes a number and spits out a value CSS will be happy with. To avoid
+ * subpixel layout issues, the value is rounded to the nearest integral value.
+ * @param {number} pixels The number of pixels.
+ * @return {string} e.g. '16px'.
+ */
+ function toCssPx(pixels) {
+ if (!window.isFinite(pixels))
+ console.error('Pixel value is not a number: ' + pixels);
+ return Math.round(pixels) + 'px';
+ }
+
+ /**
+ * Users complain they occasionaly use doubleclicks instead of clicks
+ * (http://crbug.com/140364). To fix it we freeze click handling for
+ * the doubleclick time interval.
+ * @param {MouseEvent} e Initial click event.
+ */
+ function swallowDoubleClick(e) {
+ var doc = e.target.ownerDocument;
+ var counter = Math.min(1, e.detail);
+ function swallow(e) {
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ function onclick(e) {
+ if (e.detail > counter) {
+ counter = e.detail;
+ // Swallow the click since it's a click inside the doubleclick timeout.
+ swallow(e);
+ } else {
+ // Stop tracking clicks and let regular handling.
+ doc.removeEventListener('dblclick', swallow, true);
+ doc.removeEventListener('click', onclick, true);
+ }
+ }
+ // The following 'click' event (if e.type == 'mouseup') mustn't be taken
+ // into account (it mustn't stop tracking clicks). Start event listening
+ // after zero timeout.
+ setTimeout(function() {
+ doc.addEventListener('click', onclick, true);
+ doc.addEventListener('dblclick', swallow, true);
+ }, 0);
+ }
+
+ return {
+ decorate: decorate,
+ define: define,
+ limitInputWidth: limitInputWidth,
+ toCssPx: toCssPx,
+ swallowDoubleClick: swallowDoubleClick
+ };
+});
diff --git a/chrome/util.js b/chrome/util.js
new file mode 100644
index 0000000..87d4d03
--- /dev/null
+++ b/chrome/util.js
@@ -0,0 +1,505 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// // Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview Assertion support.
+ */
+
+/**
+ * Verify |condition| is truthy and return |condition| if so.
+ * @template T
+ * @param {T} condition A condition to check for truthiness. Note that this
+ * may be used to test whether a value is defined or not, and we don't want
+ * to force a cast to Boolean.
+ * @param {string=} opt_message A message to show on failure.
+ * @return {T} A non-null |condition|.
+ */
+function assert(condition, opt_message) {
+ if (!condition) {
+ var message = 'Assertion failed';
+ if (opt_message)
+ message = message + ': ' + opt_message;
+ var error = new Error(message);
+ var global = function() {
+ return this;
+ }();
+ if (global.traceAssertionsForTesting)
+ console.warn(error.stack);
+ throw error;
+ }
+ return condition;
+}
+
+/**
+ * Call this from places in the code that should never be reached.
+ *
+ * For example, handling all the values of enum with a switch() like this:
+ *
+ * function getValueFromEnum(enum) {
+ * switch (enum) {
+ * case ENUM_FIRST_OF_TWO:
+ * return first
+ * case ENUM_LAST_OF_TWO:
+ * return last;
+ * }
+ * assertNotReached();
+ * return document;
+ * }
+ *
+ * This code should only be hit in the case of serious programmer error or
+ * unexpected input.
+ *
+ * @param {string=} opt_message A message to show when this is hit.
+ */
+function assertNotReached(opt_message) {
+ assert(false, opt_message || 'Unreachable code hit');
+}
+
+/**
+ * @param {*} value The value to check.
+ * @param {function(new: T, ...)} type A user-defined constructor.
+ * @param {string=} opt_message A message to show when this is hit.
+ * @return {T}
+ * @template T
+ */
+function assertInstanceof(value, type, opt_message) {
+ // We don't use assert immediately here so that we avoid constructing an error
+ // message if we don't have to.
+ if (!(value instanceof type)) {
+ assertNotReached(
+ opt_message ||
+ 'Value ' + value + ' is not a[n] ' + (type.name || typeof type));
+ }
+ return value;
+}
+
+
+/**
+ * Alias for document.getElementById. Found elements must be HTMLElements.
+ * @param {string} id The ID of the element to find.
+ * @return {HTMLElement} The found element or null if not found.
+ */
+function $(id) {
+ // Disable getElementById restriction here, since we are instructing other
+ // places to re-use the $() that is defined here.
+ // eslint-disable-next-line no-restricted-properties
+ var el = document.getElementById(id);
+ return el ? assertInstanceof(el, HTMLElement) : null;
+}
+
+// TODO(devlin): This should return SVGElement, but closure compiler is missing
+// those externs.
+/**
+ * Alias for document.getElementById. Found elements must be SVGElements.
+ * @param {string} id The ID of the element to find.
+ * @return {Element} The found element or null if not found.
+ */
+function getSVGElement(id) {
+ // Disable getElementById restriction here, since it is not suitable for SVG
+ // elements.
+ // eslint-disable-next-line no-restricted-properties
+ var el = document.getElementById(id);
+ return el ? assertInstanceof(el, Element) : null;
+}
+
+/**
+ * Add an accessible message to the page that will be announced to
+ * users who have spoken feedback on, but will be invisible to all
+ * other users. It's removed right away so it doesn't clutter the DOM.
+ * @param {string} msg The text to be pronounced.
+ */
+function announceAccessibleMessage(msg) {
+ var element = document.createElement('div');
+ element.setAttribute('aria-live', 'polite');
+ element.style.position = 'fixed';
+ element.style.left = '-9999px';
+ element.style.height = '0px';
+ element.innerText = msg;
+ document.body.appendChild(element);
+ window.setTimeout(function() {
+ document.body.removeChild(element);
+ }, 0);
+}
+
+/**
+ * Generates a CSS url string.
+ * @param {string} s The URL to generate the CSS url for.
+ * @return {string} The CSS url string.
+ */
+function url(s) {
+ // http://www.w3.org/TR/css3-values/#uris
+ // Parentheses, commas, whitespace characters, single quotes (') and double
+ // quotes (") appearing in a URI must be escaped with a backslash
+ var s2 = s.replace(/(\(|\)|\,|\s|\'|\"|\\)/g, '\\$1');
+ // WebKit has a bug when it comes to URLs that end with \
+ // https://bugs.webkit.org/show_bug.cgi?id=28885
+ if (/\\\\$/.test(s2)) {
+ // Add a space to work around the WebKit bug.
+ s2 += ' ';
+ }
+ return 'url("' + s2 + '")';
+}
+
+/**
+ * Parses query parameters from Location.
+ * @param {Location} location The URL to generate the CSS url for.
+ * @return {Object} Dictionary containing name value pairs for URL
+ */
+function parseQueryParams(location) {
+ var params = {};
+ var query = unescape(location.search.substring(1));
+ var vars = query.split('&');
+ for (var i = 0; i < vars.length; i++) {
+ var pair = vars[i].split('=');
+ params[pair[0]] = pair[1];
+ }
+ return params;
+}
+
+/**
+ * Creates a new URL by appending or replacing the given query key and value.
+ * Not supporting URL with username and password.
+ * @param {Location} location The original URL.
+ * @param {string} key The query parameter name.
+ * @param {string} value The query parameter value.
+ * @return {string} The constructed new URL.
+ */
+function setQueryParam(location, key, value) {
+ var query = parseQueryParams(location);
+ query[encodeURIComponent(key)] = encodeURIComponent(value);
+
+ var newQuery = '';
+ for (var q in query) {
+ newQuery += (newQuery ? '&' : '?') + q + '=' + query[q];
+ }
+
+ return location.origin + location.pathname + newQuery + location.hash;
+}
+
+/**
+ * @param {Node} el A node to search for ancestors with |className|.
+ * @param {string} className A class to search for.
+ * @return {Element} A node with class of |className| or null if none is found.
+ */
+function findAncestorByClass(el, className) {
+ return /** @type {Element} */ (findAncestor(el, function(el) {
+ return el.classList && el.classList.contains(className);
+ }));
+}
+
+/**
+ * Return the first ancestor for which the {@code predicate} returns true.
+ * @param {Node} node The node to check.
+ * @param {function(Node):boolean} predicate The function that tests the
+ * nodes.
+ * @return {Node} The found ancestor or null if not found.
+ */
+function findAncestor(node, predicate) {
+ var last = false;
+ while (node != null && !(last = predicate(node))) {
+ node = node.parentNode;
+ }
+ return last ? node : null;
+}
+
+function swapDomNodes(a, b) {
+ var afterA = a.nextSibling;
+ if (afterA == b) {
+ swapDomNodes(b, a);
+ return;
+ }
+ var aParent = a.parentNode;
+ b.parentNode.replaceChild(a, b);
+ aParent.insertBefore(b, afterA);
+}
+
+/**
+ * Disables text selection and dragging, with optional whitelist callbacks.
+ * @param {function(Event):boolean=} opt_allowSelectStart Unless this function
+ * is defined and returns true, the onselectionstart event will be
+ * surpressed.
+ * @param {function(Event):boolean=} opt_allowDragStart Unless this function
+ * is defined and returns true, the ondragstart event will be surpressed.
+ */
+function disableTextSelectAndDrag(opt_allowSelectStart, opt_allowDragStart) {
+ // Disable text selection.
+ document.onselectstart = function(e) {
+ if (!(opt_allowSelectStart && opt_allowSelectStart.call(this, e)))
+ e.preventDefault();
+ };
+
+ // Disable dragging.
+ document.ondragstart = function(e) {
+ if (!(opt_allowDragStart && opt_allowDragStart.call(this, e)))
+ e.preventDefault();
+ };
+}
+
+/**
+ * Check the directionality of the page.
+ * @return {boolean} True if Chrome is running an RTL UI.
+ */
+function isRTL() {
+ return document.documentElement.dir == 'rtl';
+}
+
+/**
+ * Get an element that's known to exist by its ID. We use this instead of just
+ * calling getElementById and not checking the result because this lets us
+ * satisfy the JSCompiler type system.
+ * @param {string} id The identifier name.
+ * @return {!HTMLElement} the Element.
+ */
+function getRequiredElement(id) {
+ return assertInstanceof(
+ $(id), HTMLElement, 'Missing required element: ' + id);
+}
+
+/**
+ * Query an element that's known to exist by a selector. We use this instead of
+ * just calling querySelector and not checking the result because this lets us
+ * satisfy the JSCompiler type system.
+ * @param {string} selectors CSS selectors to query the element.
+ * @param {(!Document|!DocumentFragment|!Element)=} opt_context An optional
+ * context object for querySelector.
+ * @return {!HTMLElement} the Element.
+ */
+function queryRequiredElement(selectors, opt_context) {
+ var element = (opt_context || document).querySelector(selectors);
+ return assertInstanceof(
+ element, HTMLElement, 'Missing required element: ' + selectors);
+}
+
+// Handle click on a link. If the link points to a chrome: or file: url, then
+// call into the browser to do the navigation.
+['click', 'auxclick'].forEach(function(eventName) {
+ document.addEventListener(eventName, function(e) {
+ if (e.button > 1)
+ return; // Ignore buttons other than left and middle.
+ if (e.defaultPrevented)
+ return;
+
+ var eventPath = e.path;
+ var anchor = null;
+ if (eventPath) {
+ for (var i = 0; i < eventPath.length; i++) {
+ var element = eventPath[i];
+ if (element.tagName === 'A' && element.href) {
+ anchor = element;
+ break;
+ }
+ }
+ }
+
+ // Fallback if Event.path is not available.
+ var el = e.target;
+ if (!anchor && el.nodeType == Node.ELEMENT_NODE &&
+ el.webkitMatchesSelector('A, A *')) {
+ while (el.tagName != 'A') {
+ el = el.parentElement;
+ }
+ anchor = el;
+ }
+
+ if (!anchor)
+ return;
+
+ anchor = /** @type {!HTMLAnchorElement} */ (anchor);
+ if ((anchor.protocol == 'file:' || anchor.protocol == 'about:') &&
+ (e.button == 0 || e.button == 1)) {
+ chrome.send('navigateToUrl', [
+ anchor.href, anchor.target, e.button, e.altKey, e.ctrlKey, e.metaKey,
+ e.shiftKey
+ ]);
+ e.preventDefault();
+ }
+ });
+});
+
+/**
+ * Creates a new URL which is the old URL with a GET param of key=value.
+ * @param {string} url The base URL. There is not sanity checking on the URL so
+ * it must be passed in a proper format.
+ * @param {string} key The key of the param.
+ * @param {string} value The value of the param.
+ * @return {string} The new URL.
+ */
+function appendParam(url, key, value) {
+ var param = encodeURIComponent(key) + '=' + encodeURIComponent(value);
+
+ if (url.indexOf('?') == -1)
+ return url + '?' + param;
+ return url + '&' + param;
+}
+
+/**
+ * Creates an element of a specified type with a specified class name.
+ * @param {string} type The node type.
+ * @param {string} className The class name to use.
+ * @return {Element} The created element.
+ */
+function createElementWithClassName(type, className) {
+ var elm = document.createElement(type);
+ elm.className = className;
+ return elm;
+}
+
+/**
+ * transitionend does not always fire (e.g. when animation is aborted
+ * or when no paint happens during the animation). This function sets up
+ * a timer and emulate the event if it is not fired when the timer expires.
+ * @param {!HTMLElement} el The element to watch for transitionend.
+ * @param {number=} opt_timeOut The maximum wait time in milliseconds for the
+ * transitionend to happen. If not specified, it is fetched from |el|
+ * using the transitionDuration style value.
+ */
+function ensureTransitionEndEvent(el, opt_timeOut) {
+ if (opt_timeOut === undefined) {
+ var style = getComputedStyle(el);
+ opt_timeOut = parseFloat(style.transitionDuration) * 1000;
+
+ // Give an additional 50ms buffer for the animation to complete.
+ opt_timeOut += 50;
+ }
+
+ var fired = false;
+ el.addEventListener('transitionend', function f(e) {
+ el.removeEventListener('transitionend', f);
+ fired = true;
+ });
+ window.setTimeout(function() {
+ if (!fired)
+ cr.dispatchSimpleEvent(el, 'transitionend', true);
+ }, opt_timeOut);
+}
+
+/**
+ * Alias for document.scrollTop getter.
+ * @param {!HTMLDocument} doc The document node where information will be
+ * queried from.
+ * @return {number} The Y document scroll offset.
+ */
+function scrollTopForDocument(doc) {
+ return doc.documentElement.scrollTop || doc.body.scrollTop;
+}
+
+/**
+ * Alias for document.scrollTop setter.
+ * @param {!HTMLDocument} doc The document node where information will be
+ * queried from.
+ * @param {number} value The target Y scroll offset.
+ */
+function setScrollTopForDocument(doc, value) {
+ doc.documentElement.scrollTop = doc.body.scrollTop = value;
+}
+
+/**
+ * Alias for document.scrollLeft getter.
+ * @param {!HTMLDocument} doc The document node where information will be
+ * queried from.
+ * @return {number} The X document scroll offset.
+ */
+function scrollLeftForDocument(doc) {
+ return doc.documentElement.scrollLeft || doc.body.scrollLeft;
+}
+
+/**
+ * Alias for document.scrollLeft setter.
+ * @param {!HTMLDocument} doc The document node where information will be
+ * queried from.
+ * @param {number} value The target X scroll offset.
+ */
+function setScrollLeftForDocument(doc, value) {
+ doc.documentElement.scrollLeft = doc.body.scrollLeft = value;
+}
+
+/**
+ * Replaces '&', '<', '>', '"', and ''' characters with their HTML encoding.
+ * @param {string} original The original string.
+ * @return {string} The string with all the characters mentioned above replaced.
+ */
+function HTMLEscape(original) {
+ return original.replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''');
+}
+
+/**
+ * Shortens the provided string (if necessary) to a string of length at most
+ * |maxLength|.
+ * @param {string} original The original string.
+ * @param {number} maxLength The maximum length allowed for the string.
+ * @return {string} The original string if its length does not exceed
+ * |maxLength|. Otherwise the first |maxLength| - 1 characters with '...'
+ * appended.
+ */
+function elide(original, maxLength) {
+ if (original.length <= maxLength)
+ return original;
+ return original.substring(0, maxLength - 1) + '\u2026';
+}
+
+/**
+ * Quote a string so it can be used in a regular expression.
+ * @param {string} str The source string.
+ * @return {string} The escaped string.
+ */
+function quoteString(str) {
+ return str.replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g, '\\$1');
+}
+
+/**
+ * Calls |callback| and stops listening the first time any event in |eventNames|
+ * is triggered on |target|.
+ * @param {!EventTarget} target
+ * @param {!Array|string} eventNames Array or space-delimited string of
+ * event names to listen to (e.g. 'click mousedown').
+ * @param {function(!Event)} callback Called at most once. The
+ * optional return value is passed on by the listener.
+ */
+function listenOnce(target, eventNames, callback) {
+ if (!Array.isArray(eventNames))
+ eventNames = eventNames.split(/ +/);
+
+ var removeAllAndCallCallback = function(event) {
+ eventNames.forEach(function(eventName) {
+ target.removeEventListener(eventName, removeAllAndCallCallback, false);
+ });
+ return callback(event);
+ };
+
+ eventNames.forEach(function(eventName) {
+ target.addEventListener(eventName, removeAllAndCallCallback, false);
+ });
+}
+
+// /* is_ios */
+
+/**
+ * Helper to convert callback-based define() API to a promise-based API.
+ * @suppress {undefinedVars}
+ * @param {!Array} moduleNames
+ * @return {!Promise}
+ */
+function importModules(moduleNames) {
+ return new Promise(function(resolve) {
+ define(moduleNames, function() {
+ resolve(Array.from(arguments));
+ });
+ });
+}
+
+/**
+ * @param {!Event} e
+ * @return {boolean} Whether a modifier key was down when processing |e|.
+ */
+function hasKeyModifiers(e) {
+ return !!(e.altKey || e.ctrlKey || e.metaKey || e.shiftKey);
+}
diff --git a/chrome/widgets.css b/chrome/widgets.css
new file mode 100644
index 0000000..0c8fa78
--- /dev/null
+++ b/chrome/widgets.css
@@ -0,0 +1,281 @@
+/* Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file. */
+
+/* This file defines styles for form controls. The order of rule blocks is
+ * important as there are some rules with equal specificity that rely on order
+ * as a tiebreaker. These are marked with OVERRIDE. */
+
+@import url(action_link.css);
+
+/* Default state **************************************************************/
+
+:-webkit-any(button,
+ input[type='button'],
+ input[type='submit']):not(.custom-appearance),
+select,
+input[type='checkbox'],
+input[type='radio'] {
+ -webkit-appearance: none;
+ background-image: -webkit-linear-gradient(#ededed, #ededed 38%, #dedede);
+ border: 1px solid rgba(0, 0, 0, 0.25);
+ border-radius: 2px;
+ box-shadow: 0 1px 0 rgba(0, 0, 0, 0.08),
+ inset 0 1px 2px rgba(255, 255, 255, 0.75);
+ color: #444;
+ font: inherit;
+ margin: 0 1px 0 0;
+ outline: none;
+ text-shadow: 0 1px 0 rgb(240, 240, 240);
+ user-select: none;
+}
+
+:-webkit-any(button,
+ input[type='button'],
+ input[type='submit']):not(.custom-appearance),
+select {
+ min-height: 2em;
+ min-width: 4em;
+ padding-bottom: 1px;
+
+ padding-top: 1px;
+}
+
+:-webkit-any(button,
+ input[type='button'],
+ input[type='submit']):not(.custom-appearance) {
+ -webkit-padding-end: 10px;
+ -webkit-padding-start: 10px;
+}
+
+select {
+ -webkit-appearance: none;
+ -webkit-padding-end: 24px;
+ -webkit-padding-start: 10px;
+ /* OVERRIDE */
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAAICAQAAACxSAwfAAAAUklEQVQY02P4z0AMRGZGMaShwCisyhITmb8huMzfEhOxKvuvsGAh208Ik+3ngoX/FbBbClcIUcSAw21QhXxfIIrwKAMpfNsEUYRXGVCEFc6CQwBqq4CCCtU4VgAAAABJRU5ErkJggg==),
+ -webkit-linear-gradient(#ededed, #ededed 38%, #dedede);
+ background-position: right center;
+ background-repeat: no-repeat;
+}
+
+html[dir='rtl'] select {
+ background-position: center left;
+}
+
+input[type='checkbox'] {
+ height: 13px;
+ position: relative;
+ vertical-align: middle;
+ width: 13px;
+}
+
+input[type='radio'] {
+ /* OVERRIDE */
+ border-radius: 100%;
+ height: 15px;
+ position: relative;
+ vertical-align: middle;
+ width: 15px;
+}
+
+/* TODO(estade): add more types here? */
+input[type='number'],
+input[type='password'],
+input[type='search'],
+input[type='text'],
+input[type='url'],
+input:not([type]),
+textarea {
+ border: 1px solid #bfbfbf;
+ border-radius: 2px;
+ box-sizing: border-box;
+ color: #444;
+ font: inherit;
+ margin: 0;
+ /* Use min-height to accommodate addditional padding for touch as needed. */
+ min-height: 2em;
+ outline: none;
+ padding: 3px;
+
+}
+
+input[type='search'] {
+ -webkit-appearance: textfield;
+ /* NOTE: Keep a relatively high min-width for this so we don't obscure the end
+ * of the default text in relatively spacious languages (i.e. German). */
+ min-width: 160px;
+}
+
+/* Checked ********************************************************************/
+
+input[type='checkbox']:checked::before {
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAALCAQAAAADpb+tAAAAaElEQVR4Xl3PIQoCQQCF4Y8JW42D1bDZ4iVEjDbxFpstYhC7eIVBZHkXFGw734sv/TqDQQ8Xb1udja/I8igeIm7Aygj2IpoKTGZnVRNxAHYi4iPiDlA9xX+aNQDFySziqDN6uSp6y7ofEMwZ05uUZRkAAAAASUVORK5CYII=);
+ background-size: 100% 100%;
+ content: '';
+ display: block;
+ height: 100%;
+ user-select: none;
+ width: 100%;
+}
+
+input[type='radio']:checked::before {
+ background-color: #666;
+ border-radius: 100%;
+ bottom: 3px;
+ content: '';
+ display: block;
+ left: 3px;
+ position: absolute;
+ right: 3px;
+ top: 3px;
+}
+
+
+/* Hover **********************************************************************/
+
+:enabled:hover:-webkit-any(
+ select,
+ input[type='checkbox'],
+ input[type='radio'],
+ :-webkit-any(
+ button,
+ input[type='button'],
+ input[type='submit']):not(.custom-appearance)) {
+ background-image: -webkit-linear-gradient(#f0f0f0, #f0f0f0 38%, #e0e0e0);
+ border-color: rgba(0, 0, 0, 0.3);
+ box-shadow: 0 1px 0 rgba(0, 0, 0, 0.12),
+ inset 0 1px 2px rgba(255, 255, 255, 0.95);
+ color: black;
+}
+
+:enabled:hover:-webkit-any(select) {
+ /* OVERRIDE */
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAAICAQAAACxSAwfAAAAUklEQVQY02P4z0AMRGZGMaShwCisyhITmb8huMzfEhOxKvuvsGAh208Ik+3ngoX/FbBbClcIUcSAw21QhXxfIIrwKAMpfNsEUYRXGVCEFc6CQwBqq4CCCtU4VgAAAABJRU5ErkJggg==),
+ -webkit-linear-gradient(#f0f0f0, #f0f0f0 38%, #e0e0e0);
+}
+
+
+/* Active *********************************************************************/
+
+:enabled:active:-webkit-any(
+ select,
+ input[type='checkbox'],
+ input[type='radio'],
+ :-webkit-any(
+ button,
+ input[type='button'],
+ input[type='submit']):not(.custom-appearance)) {
+ background-image: -webkit-linear-gradient(#e7e7e7, #e7e7e7 38%, #d7d7d7);
+ box-shadow: none;
+ text-shadow: none;
+}
+
+:enabled:active:-webkit-any(select) {
+ /* OVERRIDE */
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAAICAQAAACxSAwfAAAAUklEQVQY02P4z0AMRGZGMaShwCisyhITmb8huMzfEhOxKvuvsGAh208Ik+3ngoX/FbBbClcIUcSAw21QhXxfIIrwKAMpfNsEUYRXGVCEFc6CQwBqq4CCCtU4VgAAAABJRU5ErkJggg==),
+ -webkit-linear-gradient(#e7e7e7, #e7e7e7 38%, #d7d7d7);
+}
+
+/* Disabled *******************************************************************/
+
+:disabled:-webkit-any(
+ button,
+ input[type='button'],
+ input[type='submit']):not(.custom-appearance),
+select:disabled {
+ background-image: -webkit-linear-gradient(#f1f1f1, #f1f1f1 38%, #e6e6e6);
+ border-color: rgba(80, 80, 80, 0.2);
+ box-shadow: 0 1px 0 rgba(80, 80, 80, 0.08),
+ inset 0 1px 2px rgba(255, 255, 255, 0.75);
+ color: #aaa;
+}
+
+select:disabled {
+ /* OVERRIDE */
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAAICAQAAACxSAwfAAAASklEQVQY02P4z0AMRGZGMaShwCisyhITG/4jw8RErMr+KyxYiFC0YOF/BeyWIikEKWLA4Ta4QogiPMpACt82QRThVQYUYYWz4BAAGr6Ii6kEPacAAAAASUVORK5CYII=),
+ -webkit-linear-gradient(#f1f1f1, #f1f1f1 38%, #e6e6e6);
+}
+
+input:disabled:-webkit-any([type='checkbox'],
+ [type='radio']) {
+ opacity: .75;
+}
+
+input:disabled:-webkit-any([type='password'],
+ [type='search'],
+ [type='text'],
+ [type='url'],
+ :not([type])) {
+ color: #999;
+}
+
+/* Focus **********************************************************************/
+
+:enabled:focus:-webkit-any(
+ select,
+ input[type='checkbox'],
+ input[type='number'],
+ input[type='password'],
+ input[type='radio'],
+ input[type='search'],
+ input[type='text'],
+ input[type='url'],
+ input:not([type]),
+ :-webkit-any(
+ button,
+ input[type='button'],
+ input[type='submit']):not(.custom-appearance)) {
+ /* We use border color because it follows the border radius (unlike outline).
+ * This is particularly noticeable on mac. */
+ border-color: rgb(77, 144, 254);
+ outline: none;
+ /* OVERRIDE */
+ transition: border-color 200ms;
+}
+
+/* Checkbox/radio helpers ******************************************************
+ *
+ * .checkbox and .radio classes wrap labels. Checkboxes and radios should use
+ * these classes with the markup structure:
+ *
+ *
+ *
+ *
+ */
+
+:-webkit-any(.checkbox, .radio) label {
+ /* Don't expand horizontally: . */
+ align-items: center;
+ display: inline-flex;
+ padding-bottom: 7px;
+ padding-top: 7px;
+ user-select: none;
+}
+
+:-webkit-any(.checkbox, .radio) label input {
+ flex-shrink: 0;
+}
+
+:-webkit-any(.checkbox, .radio) label input ~ span {
+ -webkit-margin-start: 0.6em;
+ /* Make sure long spans wrap at the same horizontal position they start. */
+ display: block;
+}
+
+:-webkit-any(.checkbox, .radio) label:hover {
+ color: black;
+}
+
+label > input:disabled:-webkit-any([type='checkbox'], [type='radio']) ~ span {
+ color: #999;
+}
+
+extensionview {
+ display: inline-block;
+ height: 300px;
+ width: 300px;
+}
diff --git a/index.nginx-debian.html b/index.nginx-debian.html
index df4a717..63433c4 100644
--- a/index.nginx-debian.html
+++ b/index.nginx-debian.html
@@ -65,7 +65,6 @@ console.log("Your screen resolution = " +
-
Hugo minx demo Hugo ananka demo
@@ -82,6 +81,7 @@ console.log("Your screen resolution = " +
Show your browser details Parsing user agent
+blank page. remember to start here. java test1 java test2 java test3
@@ -92,6 +92,12 @@ console.log("Your screen resolution = " +
detecting fonts Font Face Observer Web Font Loader
+Web GL status (also tests the browser)
+Chrome internal GPU details
+Chrome internal GPU details
+Firefox support details
+
+