diff --git a/blank.html b/blank.html new file mode 100644 index 0000000..3b173a6 --- /dev/null +++ b/blank.html @@ -0,0 +1,9 @@ + + + +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: + *
+   * var List = cr.ui.define('list');
+   * List.prototype = {
+   *   __proto__: HTMLUListElement.prototype,
+   *   decorate: function() {
+   *     ...
+   *   },
+   *   ...
+   * };
+   * 
+ * + * @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
+ +

@@ -101,11 +107,46 @@ console.log("Your screen resolution = " +
Official Mozilla Window Object documentation
-Official webgl troubleshooting site
+Official webgl troubleshooting site
Official webgl troubleshooting site (local copy)

+

All cookies

+ +

All objects in window.navigator

-

All objects in window.screen.orientation